////////////////////////////////////////////////////////
//
// 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"

#define DEB(x) x

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_imgVector(0)
{
	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);
      quicktime_close(m_streamfile);
      m_haveMovie = false;
    }
}

/////////////////////////////////////////////////////////
// openMess
//
/////////////////////////////////////////////////////////
void pix_movie :: openMess(t_symbol *filename)
{
     char buf[MAXPDSTRING];
     canvas_makefilename(getCanvas(), filename->s_name, buf, MAXPDSTRING);
     
     if (!quicktime_check_sig(buf)) {
	  post("pix_movie: %s not a quicktime 3 file",buf);      
	  return;
     }
     
     m_streamfile = quicktime_open(buf, 1, 0);
     if (!m_streamfile) {
	  post("pix_movie: %s could not open quicktime file",buf);      
	  return;
     }
     
     m_haveMovie = 1;
     m_reqFrame = 0;
     m_curFrame = -1;
     
     
     m_numvideotracks = quicktime_video_tracks(m_streamfile);
     m_currentvtrack = 0;
     
     
     // Get the length of the movie
     m_numFrames = quicktime_video_length(m_streamfile, m_currentvtrack);
     m_xsize = quicktime_video_width(m_streamfile,  m_currentvtrack);
     m_ysize = quicktime_video_height(m_streamfile, m_currentvtrack);
     int depth = quicktime_video_depth(m_streamfile, m_currentvtrack);
     char compress[6];
     
     DEB(post("checking compression"));
     strncpy(compress,quicktime_video_compressor(m_streamfile,  m_currentvtrack),6);
     
     DEB(post("compress"));
     post("pix_video: Video compression %s, xsize %d, ysize %d, depth %d",
	  compress,
	  m_xsize,m_ysize, depth);
     
     m_pixBlock.image.csize = depth/8;
     if (m_pixBlock.image.csize == 3)
	  m_pixBlock.image.format = GL_RGB;
     
     if (!quicktime_supported_video(m_streamfile, m_currentvtrack)) {
	  post("video format %s not supported",
	       compress);
	  return;
     }
     

     // 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_pixBlock.image.data = new unsigned char[dataSize];
	  memset(m_pixBlock.image.data, 0, dataSize);
     }
     

     {
	  int i;
	  
	  if (m_imgVector) delete m_imgVector;
	  m_imgVector = new unsigned char*[powYSize];
	  m_imgVector[0] = m_pixBlock.image.data;
	  for (i=1;i<powYSize;i++) {
	       m_imgVector[i] = m_imgVector[i-1] + powXSize*m_pixBlock.image.csize;
	  }

	  m_imgVector[powYSize-1] = m_pixBlock.image.data;
	  for (i=powYSize-2;i>=0;i--) {
	       m_imgVector[i] = m_imgVector[i+1] + powXSize*m_pixBlock.image.csize;
	  }
	  
     } 

     // 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 = 1 - m_yRatio;
     
     m_coords[1].s = m_xRatio;
     m_coords[1].t = 1 - m_yRatio;
     
     m_coords[2].s = m_xRatio;
     m_coords[2].t = 1.0f;
     
     m_coords[3].s = 0.f;
     m_coords[3].t = 1.0f;
     
     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;

	     quicktime_set_video_position(m_streamfile, m_curFrame, m_currentvtrack);	     
	     quicktime_decode_video(m_streamfile, m_imgVector , m_currentvtrack);
	}
	
	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);
}
