////////////////////////////////////////////////////////
//
// GEM - Graphics Environment for Multimedia
//
// mark@danks.org
//
// Implementation file 
//
//    Copyright (c) 1997-1999 Mark Danks.
//    For information on usage and redistribution, and for a DISCLAIMER OF ALL
//    WARRANTIES, see the file, "GEM.LICENSE.TERMS" in this distribution.
//
/////////////////////////////////////////////////////////

#include "../Pixes/pix_movie.h"

#include "Base/GemCache.h"
#include "Base/GemMan.h"
#include "Base/GemWinCreate.h"

static inline int powerOfTwo(int value)
{
    int x = 1;
    while(x < value) x <<= 1;
    return(x);
}

CPPEXTERN_NEW_WITH_ONE_ARG(pix_movie, t_symbol *, A_DEFSYM)

/////////////////////////////////////////////////////////
//
// pix_movie
//
/////////////////////////////////////////////////////////
// Constructor
//
/////////////////////////////////////////////////////////
pix_movie :: pix_movie(t_symbol *filename)
    	   : m_haveMovie(0), m_swap(1), m_colorSwap(0),
		     m_xRatio(1.f), m_yRatio(1.f),
		     m_reqFrame(0), m_curFrame(0), m_textureObj(0), m_frame(NULL)
{
	m_dataSize[0] = m_dataSize[1] = m_dataSize[2] = 0;

    inlet_new(this->x_obj, &this->x_obj->ob_pd, gensym("float"), gensym("img_num"));

	// initialize the pix block data
    m_pixBlock.image.data = NULL;
    m_pixBlock.image.xsize = 0;
    m_pixBlock.image.ysize = 0;
    m_pixBlock.image.csize = 4;
    m_pixBlock.image.format = GL_RGBA;
    m_pixBlock.image.type = GL_UNSIGNED_BYTE;

    // make sure that there are some characters
    if (filename->s_name[0])
		openMess(filename);
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
pix_movie :: ~pix_movie()
{
    // Clean up the movie
    if (m_haveMovie)
    {
//	  	AVIStreamGetFrameClose(m_getFrame);
		m_haveMovie = false;
    }
}

/////////////////////////////////////////////////////////
// openMess
//
/////////////////////////////////////////////////////////
void pix_movie :: openMess(t_symbol *filename)
{
    char buf[MAXPDSTRING];
    canvas_makefilename(getCanvas(), filename->s_name, buf, MAXPDSTRING);

    m_streamfile = fopen (buf, "rb");

    SetMPEGOption (MPEG_DITHER, 
		  (1) ? (int) FULL_COLOR_DITHER : 
		                 (int) ORDERED_DITHER);
	// Opens the AVI stream
    if (!OpenMPEG (m_streamfile, &m_streamVid))
    {
	 error("GEM: pix_movie: Unable to open file: %s", buf);
	 return;
    }

    if (m_frame)
	 delete [] m_frame;

    m_haveMovie = 1;
    m_reqFrame = 0;
    m_curFrame = -1;

    // Get the length of the movie
    m_numFrames = 2000; // psi.dwLength;
    
    m_xsize = m_streamVid.Width;
    m_ysize = m_streamVid.Height;
    
    // allocate a buffer that is the next power of two of the movie
    const int powXSize = powerOfTwo(m_xsize);
    const int powYSize = powerOfTwo(m_ysize);
    
    if (m_pixBlock.image.xsize != powXSize ||
	m_pixBlock.image.ysize != powYSize)
    {
	 delete [] m_pixBlock.image.data;
	 m_pixBlock.image.xsize = powXSize;
	 m_pixBlock.image.ysize = powYSize;
	 int dataSize = m_pixBlock.image.xsize * m_pixBlock.image.ysize * m_pixBlock.image.csize;
	 m_frame = new char[dataSize];
	 m_pixBlock.image.data = (unsigned char*) m_frame; //new unsigned char[dataSize];
	 memset(m_pixBlock.image.data, 0, dataSize);
    }
    
    // ratio for the texture map coordinates
    m_xRatio = (float)m_xsize / (float)powXSize;
    m_yRatio = (float)m_ysize / (float)powYSize;
    
    m_coords[0].s = 0.f;
    m_coords[0].t = 0.f;
    
    m_coords[1].s = m_xRatio;
    m_coords[1].t = 0.f;
    
    m_coords[2].s = m_xRatio;
    m_coords[2].t = m_yRatio;
    
    m_coords[3].s = 0.f;
    m_coords[3].t = m_yRatio;
    
    post("GEM: pix_movie: Loaded file: %s with %d frames", buf, m_numFrames);
}

/////////////////////////////////////////////////////////
// render
//
/////////////////////////////////////////////////////////
void pix_movie :: render(GemState *state)
{
	if (!m_haveMovie)
		return;

	// do we actually need to get a new frame from the movie
	int newImage = 0;

	if (m_reqFrame != m_curFrame)
	{
		// get the frame
		newImage = 1;
		m_curFrame = m_reqFrame;

		if (GetMPEGFrame (m_frame) == 0) {
		     RewindMPEG(m_streamfile,&m_streamVid);
		     post("end reached");
		}
		// copy over all of the data
	}
    m_pixBlock.image.data = (unsigned char* ) m_frame;
    m_pixBlock.newimage = newImage;
    state->image = &m_pixBlock;
    state->texture = 1;
    state->texCoords = m_coords;
    state->numTexCoords = 4;


}

/////////////////////////////////////////////////////////
// postrender
//
/////////////////////////////////////////////////////////
void pix_movie :: postrender(GemState *state)
{
    m_pixBlock.newimage = 0;
    state->image = NULL;
    state->texture = 0;

    glDisable(GL_TEXTURE_2D);	
}

/////////////////////////////////////////////////////////
// startRendering
//
/////////////////////////////////////////////////////////
void pix_movie :: startRendering()
{
    glGenTextures(1, &m_textureObj);
    glBindTexture(GL_TEXTURE_2D, m_textureObj);
    setUpTextureState();

    m_pixBlock.newimage = 1;
    m_dataSize[0] = m_dataSize[1] = m_dataSize[2] = 0;
}

/////////////////////////////////////////////////////////
// stopRendering
//
/////////////////////////////////////////////////////////
void pix_movie :: stopRendering()
{
    if (m_textureObj)
		glDeleteTextures(1, &m_textureObj);
    m_textureObj = 0;
	m_dataSize[0] = m_dataSize[1] = m_dataSize[2] = 0;
}

/////////////////////////////////////////////////////////
// setUpTextureState
//
/////////////////////////////////////////////////////////
void pix_movie :: setUpTextureState()
{
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

/////////////////////////////////////////////////////////
// changeImage
//
/////////////////////////////////////////////////////////
void pix_movie :: changeImage(int imgNum)
{
    if (imgNum >= m_numFrames)
    {
    	error("GEM: pix_movie: selection number too high: %d (max num is %d)", imgNum, m_numFrames);
    	return;
    }
    else if (imgNum < 0)
    {
        error("GEM: pix_movie: selection number must be > 0");
        return;
    }
    m_reqFrame = imgNum;
}

/////////////////////////////////////////////////////////
// static member function
//
/////////////////////////////////////////////////////////
void pix_movie :: obj_setupCallback(t_class *classPtr)
{
    class_addmethod(classPtr, (t_method)&pix_movie::openMessCallback,
    	    gensym("open"), A_SYMBOL, A_NULL);
    class_addmethod(classPtr, (t_method)&pix_movie::changeImageCallback,
    	    gensym("img_num"), A_FLOAT, A_NULL);
}
void pix_movie :: openMessCallback(void *data, t_symbol *filename)
{
    GetMyClass(data)->openMess(filename);
}
void pix_movie :: changeImageCallback(void *data, t_floatarg imgNum)
{
    GetMyClass(data)->changeImage((int)imgNum);
}
