/*
  pygame - Python Game Library
  Copyright (C) 2000-2001  Pete Shinners

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

  Pete Shinners
  pete@shinners.org
*/

#define PYGAMEAPI_CDROM_INTERNAL
#include "pygame.h"

#include "doc/cdrom_doc.h"
#include "pgcompat.h"

#define CDROM_MAXDRIVES 32
static SDL_CD *cdrom_drivedata[CDROM_MAXDRIVES] = {NULL};
static PyTypeObject pgCD_Type;
static PyObject *
pgCD_New(int id);
#define pgCD_Check(x) ((x)->ob_type == &pgCD_Type)

static PyObject *
cdrom_quit(PyObject *self)
{
    int loop;
    for (loop = 0; loop < CDROM_MAXDRIVES; ++loop) {
        if (cdrom_drivedata[loop]) {
            SDL_CDClose(cdrom_drivedata[loop]);
            cdrom_drivedata[loop] = NULL;
        }
    }

    if (SDL_WasInit(SDL_INIT_CDROM)) {
        SDL_QuitSubSystem(SDL_INIT_CDROM);
    }
    Py_RETURN_NONE;
}

static PyObject *
cdrom_init(PyObject *self)
{
    if (!SDL_WasInit(SDL_INIT_CDROM)) {
        if (SDL_InitSubSystem(SDL_INIT_CDROM))
            return RAISE(pgExc_SDLError, SDL_GetError());
    }
    Py_RETURN_NONE;
}

static PyObject *
get_init(PyObject *self)
{
    return PyBool_FromLong(SDL_WasInit(SDL_INIT_CDROM) != 0);
}

static void
cd_dealloc(PyObject *self)
{
    PyObject_DEL(self);
}

static PyObject *
CD(PyObject *self, PyObject *args)
{
    int id;
    if (!PyArg_ParseTuple(args, "i", &id)) {
        return NULL;
    }

    CDROM_INIT_CHECK();
    return pgCD_New(id);
}

static PyObject *
get_count(PyObject *self)
{
    CDROM_INIT_CHECK();
    return PyLong_FromLong(SDL_CDNumDrives());
}

static PyObject *
cd_init(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);

    CDROM_INIT_CHECK();
    if (!cdrom_drivedata[cd_id]) {
        cdrom_drivedata[cd_id] = SDL_CDOpen(cd_id);
        if (!cdrom_drivedata[cd_id]) {
            return RAISE(pgExc_SDLError, "Cannot initialize device");
        }
    }
    Py_RETURN_NONE;
}

static PyObject *
cd_quit(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);

    CDROM_INIT_CHECK();

    if (cdrom_drivedata[cd_id]) {
        SDL_CDClose(cdrom_drivedata[cd_id]);
        cdrom_drivedata[cd_id] = NULL;
    }
    Py_RETURN_NONE;
}

static PyObject *
cd_get_init(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    return PyBool_FromLong(cdrom_drivedata[cd_id] != NULL);
}

static PyObject *
cd_play(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int result, track, startframe, numframes, playforever = 0;
    float start = 0.0f, end = 0.0f;
    PyObject *endobject = NULL;

    if (!PyArg_ParseTuple(args, "i|fO", &track, &start, &endobject)) {
        return NULL;
    }
    if (endobject == Py_None) {
        playforever = 1;
    }
    else if (!PyArg_ParseTuple(args, "i|ff", &track, &start, &end)) {
        return NULL;
    }

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }
    SDL_CDStatus(cdrom);
    if (track < 0 || track >= cdrom->numtracks) {
        return RAISE(PyExc_IndexError, "Invalid track number");
    }
    if (cdrom->track[track].type != SDL_AUDIO_TRACK) {
        return RAISE(pgExc_SDLError, "CD track type is not audio");
    }

    /*validate times*/
    if (playforever) {
        end = start;
    }
    else if (start == end && start != 0.0f) {
        Py_RETURN_NONE;
    }

    startframe = (int)(start * CD_FPS);
    numframes = 0;
    if (startframe < 0) {
        startframe = 0;
    }
    if (end) {
        numframes = (int)((end - start) * CD_FPS);
    }
    else {
        numframes = cdrom->track[track].length - startframe;
    }
    if (numframes < 0 ||
        startframe > (int)(cdrom->track[track].length * CD_FPS)) {
        Py_RETURN_NONE;
    }

    result = SDL_CDPlayTracks(cdrom, track, startframe, 0, numframes);
    if (result == -1) {
        return RAISE(pgExc_SDLError, SDL_GetError());
    }

    Py_RETURN_NONE;
}

static PyObject *
cd_pause(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int result;

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    result = SDL_CDPause(cdrom);
    if (result == -1) {
        return RAISE(pgExc_SDLError, SDL_GetError());
    }
    Py_RETURN_NONE;
}

static PyObject *
cd_resume(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int result;

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    result = SDL_CDResume(cdrom);
    if (result == -1) {
        return RAISE(pgExc_SDLError, SDL_GetError());
    }
    Py_RETURN_NONE;
}

static PyObject *
cd_stop(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int result;

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    result = SDL_CDStop(cdrom);
    if (result == -1) {
        return RAISE(pgExc_SDLError, SDL_GetError());
    }
    Py_RETURN_NONE;
}

static PyObject *
cd_eject(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int result;

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    result = SDL_CDEject(cdrom);
    if (result == -1) {
        return RAISE(pgExc_SDLError, SDL_GetError());
    }

    Py_RETURN_NONE;
}

static PyObject *
cd_get_empty(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int status;

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    status = SDL_CDStatus(cdrom);
    return PyLong_FromLong(status == CD_TRAYEMPTY);
}

static PyObject *
cd_get_busy(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int status;

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    status = SDL_CDStatus(cdrom);
    return PyLong_FromLong(status == CD_PLAYING);
}

static PyObject *
cd_get_paused(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int status;

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    status = SDL_CDStatus(cdrom);
    return PyLong_FromLong(status == CD_PAUSED);
}

static PyObject *
cd_get_current(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int track;
    float seconds;

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    SDL_CDStatus(cdrom);
    track = cdrom->cur_track;
    seconds = cdrom->cur_frame / (float)CD_FPS;

    return Py_BuildValue("(if)", track, seconds);
}

static PyObject *
cd_get_numtracks(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    SDL_CDStatus(cdrom);
    return PyLong_FromLong(cdrom->numtracks);
}

static PyObject *
cd_get_id(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    return PyLong_FromLong(cd_id);
}

static PyObject *
cd_get_name(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    CDROM_INIT_CHECK();
    return PyUnicode_FromString(SDL_CDName(cd_id));
}

static PyObject *
cd_get_track_audio(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int track;

    if (!PyArg_ParseTuple(args, "i", &track)) {
        return NULL;
    }

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }
    SDL_CDStatus(cdrom);
    if (track < 0 || track >= cdrom->numtracks) {
        return RAISE(PyExc_IndexError, "Invalid track number");
    }

    return PyLong_FromLong(cdrom->track[track].type == SDL_AUDIO_TRACK);
}

static PyObject *
cd_get_track_length(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int track;

    if (!PyArg_ParseTuple(args, "i", &track)) {
        return NULL;
    }

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }
    SDL_CDStatus(cdrom);
    if (track < 0 || track >= cdrom->numtracks) {
        return RAISE(PyExc_IndexError, "Invalid track number");
    }
    if (cdrom->track[track].type != SDL_AUDIO_TRACK) {
        return PyFloat_FromDouble(0.0);
    }

    return PyFloat_FromDouble(cdrom->track[track].length / (double)CD_FPS);
}

static PyObject *
cd_get_track_start(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int track;

    if (!PyArg_ParseTuple(args, "i", &track)) {
        return NULL;
    }

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }
    SDL_CDStatus(cdrom);
    if (track < 0 || track >= cdrom->numtracks) {
        return RAISE(PyExc_IndexError, "Invalid track number");
    }

    return PyFloat_FromDouble(cdrom->track[track].offset / (double)CD_FPS);
}

static PyObject *
cd_get_all(PyObject *self, PyObject *args)
{
    int cd_id = pgCD_AsID(self);
    SDL_CD *cdrom = cdrom_drivedata[cd_id];
    int track;
    PyObject *tuple, *item;

    CDROM_INIT_CHECK();
    if (!cdrom) {
        return RAISE(pgExc_SDLError, "CD drive not initialized");
    }

    SDL_CDStatus(cdrom);
    tuple = PyTuple_New(cdrom->numtracks);
    if (!tuple) {
        return NULL;
    }
    for (track = 0; track < cdrom->numtracks; track++) {
        int audio = cdrom->track[track].type == SDL_AUDIO_TRACK;
        double start = cdrom->track[track].offset / (double)CD_FPS;
        double length = cdrom->track[track].length / (double)CD_FPS;
        double end = start + length;
        item = PyTuple_New(4);
        if (!item) {
            Py_DECREF(tuple);
            return NULL;
        }
        PyTuple_SET_ITEM(item, 0, PyLong_FromLong(audio));
        PyTuple_SET_ITEM(item, 1, PyFloat_FromDouble(start));
        PyTuple_SET_ITEM(item, 2, PyFloat_FromDouble(end));
        PyTuple_SET_ITEM(item, 3, PyFloat_FromDouble(length));
        PyTuple_SET_ITEM(tuple, track, item);
    }
    return tuple;
}

static PyMethodDef cd_methods[] = {
    {"init", cd_init, METH_NOARGS, DOC_CDINIT},
    {"quit", cd_quit, METH_NOARGS, DOC_CDQUIT},
    {"get_init", cd_get_init, METH_NOARGS, DOC_CDGETINIT},

    {"play", cd_play, METH_VARARGS, DOC_CDINIT},
    {"pause", cd_pause, METH_NOARGS, DOC_CDPAUSE},
    {"resume", cd_resume, METH_NOARGS, DOC_CDRESUME},
    {"stop", cd_stop, METH_NOARGS, DOC_CDSTOP},
    {"eject", cd_eject, METH_NOARGS, DOC_CDEJECT},

    {"get_empty", cd_get_empty, METH_NOARGS, DOC_CDGETEMPTY},
    {"get_busy", cd_get_busy, METH_NOARGS, DOC_CDGETBUSY},
    {"get_paused", cd_get_paused, METH_NOARGS, DOC_CDGETPAUSED},
    {"get_current", cd_get_current, METH_NOARGS, DOC_CDGETCURRENT},
    {"get_numtracks", cd_get_numtracks, METH_NOARGS, DOC_CDGETNUMTRACKS},
    {"get_id", cd_get_id, METH_NOARGS, DOC_CDGETINIT},
    {"get_name", cd_get_name, METH_NOARGS, DOC_CDGETNAME},
    {"get_all", cd_get_all, METH_NOARGS, DOC_CDGETALL},

    {"get_track_audio", cd_get_track_audio, METH_VARARGS, DOC_CDGETTRACKAUDIO},
    {"get_track_length", cd_get_track_length, METH_VARARGS,
     DOC_CDGETTRACKLENGTH},
    {"get_track_start", cd_get_track_start, METH_VARARGS, DOC_CDGETTRACKSTART},

    {NULL, NULL, 0, NULL}};

static PyTypeObject pgCD_Type = {
    PyVarObject_HEAD_INIT(NULL, 0) "CD", /* name */
    sizeof(pgCDObject),                  /* basic size */
    0,                                   /* itemsize */
    cd_dealloc,                          /* dealloc */
    0,                                   /* print */
    0,                                   /* getattr */
    0,                                   /* setattr */
    0,                                   /* compare */
    0,                                   /* repr */
    0,                                   /* as_number */
    0,                                   /* as_sequence */
    0,                                   /* as_mapping */
    0,                                   /* hash */
    0,                                   /* call */
    0,                                   /* str */
    0,                                   /* tp_getattro */
    0,                                   /* tp_setattro */
    0,                                   /* tp_as_buffer */
    0,                                   /* flags */
    DOC_PYGAMECDROMCD,                   /* Documentation string */
    0,                                   /* tp_traverse */
    0,                                   /* tp_clear */
    0,                                   /* tp_richcompare */
    0,                                   /* tp_weaklistoffset */
    0,                                   /* tp_iter */
    0,                                   /* tp_iternext */
    cd_methods,                          /* tp_methods */
    0,                                   /* tp_members */
    0,                                   /* tp_getset */
    0,                                   /* tp_base */
    0,                                   /* tp_dict */
    0,                                   /* tp_descr_get */
    0,                                   /* tp_descr_set */
    0,                                   /* tp_dictoffset */
    0,                                   /* tp_init */
    0,                                   /* tp_alloc */
    0,                                   /* tp_new */
};

static PyObject *
pgCD_New(int id)
{
    pgCDObject *cd;

    if (id < 0 || id >= CDROM_MAXDRIVES || id >= SDL_CDNumDrives()) {
        return RAISE(pgExc_SDLError, "Invalid cdrom device number");
    }

    cd = PyObject_NEW(pgCDObject, &pgCD_Type);
    if (!cd) {
        return NULL;
    }

    cd->id = id;

    return (PyObject *)cd;
}

static PyMethodDef _cdrom_methods[] = {
    {"init", (PyCFunction)cdrom_init, METH_NOARGS, DOC_PYGAMECDROMINIT},
    {"quit", (PyCFunction)cdrom_quit, METH_NOARGS, DOC_PYGAMECDROMQUIT},
    {"get_init", (PyCFunction)get_init, METH_NOARGS, DOC_PYGAMECDROMGETINIT},
    {"get_count", (PyCFunction)get_count, METH_NOARGS,
     DOC_PYGAMECDROMGETCOUNT},
    {"CD", CD, METH_VARARGS, DOC_PYGAMECDROMCD},
    {NULL, NULL, 0, NULL}};

MODINIT_DEFINE(cdrom)
{
    PyObject *module, *dict, *apiobj;
    int ecode;
    static void *c_api[PYGAMEAPI_CDROM_NUMSLOTS];

    static struct PyModuleDef _module = {PyModuleDef_HEAD_INIT,
                                         "cdrom",
                                         DOC_PYGAMECDROM,
                                         -1,
                                         _cdrom_methods,
                                         NULL,
                                         NULL,
                                         NULL,
                                         NULL};

    /* imported needed apis; Do this first so if there is an error
       the module is not loaded.
    */
    import_pygame_base();
    if (PyErr_Occurred()) {
        return NULL;
    }

    /* type preparation */
    if (PyType_Ready(&pgCD_Type) == -1) {
        return NULL;
    }

    /* create the module */
    module = PyModule_Create(&_module);
    if (module == NULL) {
        return NULL;
    }
    dict = PyModule_GetDict(module);

    if (PyDict_SetItemString(dict, "CDType", (PyObject *)&pgCD_Type) == -1) {
        Py_DECREF(module);
        return NULL;
    }

    /* export the c api */
    c_api[0] = &pgCD_Type;
    c_api[1] = pgCD_New;
    apiobj = encapsulate_api(c_api, "cdrom");
    if (apiobj == NULL) {
        Py_DECREF(module);
        return NULL;
    }
    ecode = PyDict_SetItemString(dict, PYGAMEAPI_LOCAL_ENTRY, apiobj);
    Py_DECREF(apiobj);

    if (ecode == -1) {
        Py_DECREF(module);
        return NULL;
    }
    return module;
}
