////////////////////////////////////////////////////////
//
// GEM - Graphics Environment for Multimedia
//
// mdanks@cybermed.ucsd.edu
//
// Implementation file 
//
//    Copyright (c) 1997-1998 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 "pix_video.h"

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

CPPEXTERN_NEW(pix_video)

/////////////////////////////////////////////////////////
//
// video
//
/////////////////////////////////////////////////////////
// Constructor
//
/////////////////////////////////////////////////////////
pix_video :: pix_video()
    	   : m_haveVideo(0), m_swap(1), m_colorSwap(0),
			 m_hWndC(NULL)
{
    m_pixBlock.image.data = NULL;
	HWND hWnd = GemMan::getConstWindowInfo().win;
	if (!hWnd)
	{
		post("Holy crap!");
		return;
	}
    
    // Connect to the daemon
	m_hWndC = capCreateCaptureWindow (
		(LPSTR) "GEM video",	// window name if pop-up 
		0,						// window style (not visible)
		0, 0, 128, 128,         // window position and dimensions
		hWnd, 0);
	if (!m_hWndC)
	{
		post("GEM: pix_video: Unable to create capture window");
		return;
	} 

	if (!capDriverConnect(m_hWndC, 0))
	{
		post("GEM: pix_video: Unable to connect to video driver");
		return;
	}

	CAPTUREPARMS params;
	if (!capCaptureGetSetup(m_hWndC, &params, sizeof(CAPTUREPARMS)))
	{
		post("GEM: pix_video: Unable to get capture parameters");
		return;
	}
	params.fYield = TRUE;
	params.fCaptureAudio = FALSE;
	if (!capCaptureSetSetup(m_hWndC, &params, sizeof(CAPTUREPARMS)))
	{
		post("GEM: pix_video: Unable to set capture parameters");
		return;
	}

	if (!capSetCallbackOnVideoStream(m_hWndC, pix_video::videoFrameCallback))
	{
		post("GEM: pix_video: Unable to set frame callback");
		return;
	}
	if (!capSetUserData(m_hWndC, this))
	{
		post("GEM: pix_video: Unable to set user data");
		return;
	}
	DWORD formSize = capGetVideoFormat(m_hWndC, NULL, 0);
	BITMAPINFO *videoFormat = (BITMAPINFO *)(new char[formSize]);
	if (!capGetVideoFormat(m_hWndC, videoFormat, formSize))
	{
		post("GEM: pix_video: Unable to get video format");
		return;
	}
	post("x: %d, y: %d, c: %d",
			(int)(videoFormat->bmiHeader.biWidth),
			(int)(videoFormat->bmiHeader.biHeight),
			(int)(videoFormat->bmiHeader.biBitCount));
	if (videoFormat->bmiHeader.biCompression == BI_RGB)
		post("bingo");
	else post("Crap");

	videoFormat->bmiHeader.biWidth = 128;
	videoFormat->bmiHeader.biHeight = 128;
	videoFormat->bmiHeader.biBitCount = 24;
	videoFormat->bmiHeader.biCompression = BI_RGB;
	videoFormat->bmiHeader.biClrUsed = 0;
	videoFormat->bmiHeader.biClrImportant = 0;
	videoFormat->bmiHeader.biSizeImage = 0;
	if (!capSetVideoFormat(m_hWndC, videoFormat, formSize))
	{
		post("GEM: pix_video: Unable to set video format");
		return;
	}
	delete videoFormat;

    m_pixBlock.image.xsize = videoFormat->bmiHeader.biWidth;
    m_pixBlock.image.ysize = videoFormat->bmiHeader.biHeight;
    m_pixBlock.image.csize = 4;
    m_pixBlock.image.format = GL_RGBA;
    m_pixBlock.image.type = GL_UNSIGNED_BYTE;
    int dataSize = m_pixBlock.image.xsize * m_pixBlock.image.ysize
    	    	     * 4 * sizeof(unsigned char);
    m_pixBlock.image.data = new unsigned char[dataSize];

	m_haveVideo = 1;
    post("GEM: pix_video: Opened video connection");
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
pix_video :: ~pix_video()
{
	if (!m_hWndC) return;

    stopTransfer();
	capDriverDisconnect(m_hWndC);
    DestroyWindow(m_hWndC);

    // Clean up the video
    if (m_haveVideo)
    {
    	post("GEM: pix_video: Closed video connection");
    }
    cleanPixBlock();
}

/////////////////////////////////////////////////////////
// videoFrame
//
/////////////////////////////////////////////////////////
void pix_video :: videoFrame(LPVIDEOHDR lpVHdr)
{
	post("yeah");
	int count = lpVHdr->dwBytesUsed;
	// notice that it is times 3 for the color!
	int dataSize = m_pixBlock.image.xsize * m_pixBlock.image.ysize
    	    	     * 3 * sizeof(unsigned char);
	if (count != dataSize)
	{
		post("Shit, not equal");
		return;
	}
	unsigned char *pixels = lpVHdr->lpData;
	unsigned char *dst = m_pixBlock.image.data;

	dataSize = m_pixBlock.image.xsize * m_pixBlock.image.ysize;
	while(dataSize--)
	{
		dst[chRed] = pixels[0];
		dst[chGreen] = pixels[1];
		dst[chBlue] = pixels[2];
		dst[chAlpha] = 255;
		dst += 4;
		pixels += 3;
	}
	post("done");
}

/////////////////////////////////////////////////////////
// render
//
/////////////////////////////////////////////////////////
void pix_video :: render(GemState *state)
{
	if (!m_haveVideo)
    {
		post("GEM: pix_video: Unable to activate video");
		return;
    }

    m_pixBlock.newimage = 1;
    state->image = &m_pixBlock;
}

/////////////////////////////////////////////////////////
// startRendering
//
/////////////////////////////////////////////////////////
void pix_video :: startRendering()
{
	if (!capCaptureSequenceNoFile(m_hWndC))
	{
		post("GEM: pix_video: Unable to start capture");
		return;
	}

	post("started!");
    m_pixBlock.newimage = 1;
}

/////////////////////////////////////////////////////////
// stopRendering
//
/////////////////////////////////////////////////////////
void pix_video :: stopRendering()
{
	capCaptureStop(m_hWndC);
}

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

/////////////////////////////////////////////////////////
// startTransfer
//
/////////////////////////////////////////////////////////
int pix_video :: startTransfer()
{
    if (!m_haveVideo) return(0);
	return(1);
}

/////////////////////////////////////////////////////////
// stopTransfer
//
/////////////////////////////////////////////////////////
int pix_video :: stopTransfer()
{
    if ( !m_haveVideo ) return(0);

    // Clean up the buffer
    
    return(1);
}

/////////////////////////////////////////////////////////
// offsetMess
//
/////////////////////////////////////////////////////////
void pix_video :: offsetMess(int x, int y)
{
    if (!m_haveVideo)
    {
    	post("GEM: Connect to video first");
    	return;
    }
    
    // stop the transfer and destroy the buffer
    if ( !stopTransfer() ) 
    {
    	post("GEM: Video: error stopping transfer");
    	return;
    }

    // start the transfer and rebuild the buffer
    if ( !startTransfer() ) 
    {
    	post("GEM: Video: error starting transfer");
    	return;
    }
}

/////////////////////////////////////////////////////////
// dimenMess
//
/////////////////////////////////////////////////////////
void pix_video :: dimenMess(int x, int y)
{
    if (!m_haveVideo)
    {
    	post("GEM: Connect to video first");
    	return;
    }
    
    // stop the transfer and destroy the buffer
    if ( !stopTransfer() ) 
    {
    	post("GEM: Video: error stopping transfer");
    	return;
    }

    m_pixBlock.image.xsize = x;
    m_pixBlock.image.ysize = y;;

    cleanPixBlock();
    int dataSize = m_pixBlock.image.xsize * m_pixBlock.image.ysize
    	    	    * 4 * sizeof(unsigned char);
    m_pixBlock.image.data = new unsigned char[dataSize];

    // start the transfer and rebuild the buffer
    if ( !startTransfer() ) 
    {
    	post("GEM: Video: error starting transfer");
    	return;
    }
}

/////////////////////////////////////////////////////////
// cleanPixBlock
//
/////////////////////////////////////////////////////////
void pix_video :: cleanPixBlock()
{
    delete [] m_pixBlock.image.data;
    m_pixBlock.image.data = NULL;
}

/////////////////////////////////////////////////////////
// swapMess
//
/////////////////////////////////////////////////////////
void pix_video :: swapMess(int state)
{
    if (state) m_swap = 1;
    else m_swap = 0;
}

/////////////////////////////////////////////////////////
// static member function
//
/////////////////////////////////////////////////////////
void pix_video :: obj_setupCallback(t_class *classPtr)
{
    class_addmethod(classPtr, (t_method)&pix_video::dimenMessCallback,
    	    gensym("dimen"), A_FLOAT, A_FLOAT, A_NULL);
    class_addmethod(classPtr, (t_method)&pix_video::offsetMessCallback,
    	    gensym("offset"), A_FLOAT, A_FLOAT, A_NULL);
    class_addmethod(classPtr, (t_method)&pix_video::swapMessCallback,
    	    gensym("swap"), A_FLOAT, A_NULL);
}
void pix_video :: dimenMessCallback(void *data, t_floatarg x, t_floatarg y)
{
    GetMyClass(data)->dimenMess((int)x, (int)y);
}
void pix_video :: offsetMessCallback(void *data, t_floatarg x, t_floatarg y)
{
    GetMyClass(data)->offsetMess((int)x, (int)y);
}
void pix_video :: swapMessCallback(void *data, t_floatarg state)
{
    GetMyClass(data)->swapMess((int)state);
}

LRESULT PASCAL pix_video :: videoFrameCallback(HWND hWnd, LPVIDEOHDR lpVHdr) 
{
	void *ptr = (void *)(capGetUserData(hWnd));
	((pix_video *)ptr)->videoFrame(lpVHdr);
    return (LRESULT) TRUE ;
}

