/*
 * VFAPI-Plugin
 *
 * Copyright (c) 2006 Peter Schlaile
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 */

#include <windows.h>
#include <winbase.h>
#include <stdio.h>
#include <stdlib.h>
#include <direct.h>

#define	VF_STREAM_VIDEO		0x00000001
#define	VF_STREAM_AUDIO		0x00000002
#define	VF_OK				0x00000000
#define	VF_ERROR			0x80004005

typedef	struct {
	DWORD	dwSize;
	DWORD	dwAPIVersion;	
	DWORD	dwVersion;	
	DWORD	dwSupportStreamType;	
	char	cPluginInfo[256];	
	char	cFileType[256]; 	
} VF_PluginInfo,*LPVF_PluginInfo;

typedef	DWORD	VF_FileHandle,*LPVF_FileHandle;

typedef	struct {
	DWORD	dwSize;			
	DWORD	dwHasStreams;		
} VF_FileInfo,*LPVF_FileInfo;

typedef	struct {
	DWORD	dwSize;			
	DWORD	dwLengthL;	
	DWORD	dwLengthH;	
	DWORD	dwRate;			
	DWORD	dwScale;		
	DWORD	dwWidth;		
	DWORD	dwHeight;		
	DWORD	dwBitCount;		
} VF_StreamInfo_Video,*LPVF_StreamInfo_Video;

typedef	struct {
	DWORD	dwSize;			
	DWORD	dwLengthL;	
	DWORD	dwLengthH;	
	DWORD	dwRate;			
	DWORD	dwScale;		
	DWORD	dwChannels;		
	DWORD	dwBitsPerSample;	
	DWORD	dwBlockAlign;		
} VF_StreamInfo_Audio,*LPVF_StreamInfo_Audio;

typedef	struct {
	DWORD	dwSize;			
	DWORD	dwFrameNumberL;	
	DWORD	dwFrameNumberH;	
	void	*lpData;		
	long	lPitch;			
} VF_ReadData_Video,*LPVF_ReadData_Video;

typedef	struct {
	DWORD	dwSize;			
	LONGLONG	dwSamplePos;	
	DWORD	dwSampleCount;		
	DWORD	dwReadedSampleCount;	
	DWORD	dwBufSize;		
	void	*lpBuf;			
} VF_ReadData_Audio,*LPVF_ReadData_Audio;

typedef	struct {
	DWORD	dwSize;
	HRESULT (__stdcall *OpenFile)(
	        char *lpFileName, LPVF_FileHandle lpFileHandle );
	HRESULT (__stdcall *CloseFile)( VF_FileHandle hFileHandle );
	HRESULT (__stdcall *GetFileInfo)( VF_FileHandle hFileHandle,
	                                  LPVF_FileInfo lpFileInfo );
	HRESULT (__stdcall *GetStreamInfo)( VF_FileHandle hFileHandle,
	                                    DWORD dwStream,void *lpStreamInfo );
	HRESULT (__stdcall *ReadData)( VF_FileHandle hFileHandle,
	                               DWORD dwStream,void *lpData );
} VF_PluginFunc,*LPVF_PluginFunc;

__declspec(dllexport) HRESULT vfGetPluginInfo( 
	LPVF_PluginInfo lpPluginInfo )
{
	if (!lpPluginInfo || lpPluginInfo->dwSize != sizeof(VF_PluginInfo)) {
		return VF_ERROR;
	}

	lpPluginInfo->dwAPIVersion = 1;
	lpPluginInfo->dwVersion = 1;
	lpPluginInfo->dwSupportStreamType = VF_STREAM_VIDEO;
	strcpy(lpPluginInfo->cPluginInfo, "Blender Frameserver");
	strcpy(lpPluginInfo->cFileType, 
	       "Blender Frame-URL-File (*.blu)|*.blu");

	return VF_OK;
}

static unsigned long getipaddress(const char *ipaddr)
{
	struct hostent  *host;
	unsigned long   ip;

	if (((ip = inet_addr(ipaddr)) == INADDR_NONE) &&
	    strcmp(ipaddr, "255.255.255.255") != 0)
	{
		if ((host = gethostbyname(ipaddr)) != NULL) {
			memcpy(&ip, host->h_addr, sizeof(ip));
		}
	}

	return (ip);
}

static void my_send(SOCKET sock, char *str)
{
	send(sock, str, strlen(str), 0);
}

static int my_recv(SOCKET sock, char *line, int maxlen)
{
	int got = 0;
	int toget = maxlen;

	while (toget > 0) {
		got = recv(sock, line, toget, 0);
		if (got <= 0) {
			return got;
		}
		toget -= got;
		line += got;
	}
	return maxlen;
}

static int my_gets(SOCKET sock, char *line, int maxlen)
{
	int last_rval = 0;

	while (((last_rval = my_recv(sock, line, 1)) == 1) && maxlen > 0) {
		if (*line == '\n') {
			line++;
			*line = 0;
			break;
		}
		else {
			line++;
			maxlen--;
		}
	}
	return last_rval;
}

typedef struct conndesc_ {
	struct sockaddr_in      addr;
	int width;
	int height;
	int start;
	int end;
	int rate;
	int ratescale;
} conndesc;



HRESULT __stdcall VF_OpenFileFunc_Blen( 
	char *lpFileName, LPVF_FileHandle lpFileHandle )
{
	conndesc * rval;
	char * host;
	char * p;
	int port;
	SOCKET s_in;
	char buf[256];
	struct sockaddr_in      addr;
	FILE *fp;

	p = lpFileName;
	while (*p && *p != '.') p++;
	if (*p) p++;
	if (strcmp(p, "blu") != 0) {
		return VF_ERROR;
	}

	fp = fopen(lpFileName, "r");
	if (!fp) {
		return VF_ERROR;
	}
	fgets(buf, 256, fp);
	fclose(fp);

	host = buf;
	p = host;
	while (*p && *p != ':') p++;
	if (*p) p++;
	p[-1] = 0;
	port = atoi(p);
	if (!port) {
		port = 8080;
	}

	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = getipaddress(host);

	s_in = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s_in < 0) {
		return VF_ERROR;
	}

	if (connect(s_in, (struct sockaddr*) &addr,
		    sizeof(addr)) < 0) {
		closesocket(s_in);
		return VF_ERROR;
	}

	rval = (conndesc *) malloc(sizeof(conndesc));

	rval->addr = addr;

	my_send(s_in, "GET /info.txt HTTP/1.0\n\n");

	for (;;) {
		char * key;
		char * val;

		if (my_gets(s_in, buf, 250) <= 0) {
			break;
		}

		key = buf;
		val = buf;
		while (*val && *val != ' ') val++;
		if (*val) {
			*val = 0;
			val++;
			
			if (strcmp(key, "width") == 0) {
				rval->width = atoi(val);
			}
			else if (strcmp(key, "height") == 0) {
				rval->height = atoi(val);
			}
			else if (strcmp(key, "start") == 0) {
				rval->start = atoi(val);
			}
			else if (strcmp(key, "end") == 0) {
				rval->end = atoi(val);
			}
			else if (strcmp(key, "rate") == 0) {
				rval->rate = atoi(val);
			}
			else if (strcmp(key, "ratescale") == 0) {
				rval->ratescale = atoi(val);
			}
		}
	}

	closesocket(s_in);

	*lpFileHandle = (VF_FileHandle) rval;

	return VF_OK;
}

HRESULT __stdcall VF_CloseFileFunc_Blen( 
	VF_FileHandle hFileHandle )
{
	free((conndesc *) hFileHandle);

	return VF_OK;
}

HRESULT __stdcall VF_GetFileInfoFunc_Blen( 
	VF_FileHandle hFileHandle,
	LPVF_FileInfo lpFileInfo )
{
	conndesc *c = (conndesc *) hFileHandle;
	if (c == 0) { 
		return VF_ERROR; 
	}

	if (lpFileInfo->dwSize != sizeof(VF_FileInfo)) {
		return VF_ERROR;
	}
	
	lpFileInfo->dwHasStreams = VF_STREAM_VIDEO;

	return VF_OK;
}

HRESULT __stdcall VF_GetStreamInfoFunc_Blen( 
	VF_FileHandle hFileHandle,
	DWORD dwStream,void *lpStreamInfo )
{
	conndesc * c = (conndesc*) hFileHandle;

	LPVF_StreamInfo_Video v = (LPVF_StreamInfo_Video) lpStreamInfo;

	if (c == 0 || dwStream != VF_STREAM_VIDEO || v == 0) { 
		return VF_ERROR;
	}

	v->dwLengthL = c->end - c->start;
	v->dwLengthH = 0;
	v->dwScale = c->ratescale;
	v->dwRate = c->rate;
	v->dwWidth = c->width;
	v->dwHeight = c->height;
	v->dwBitCount = 24;

	return VF_OK;
}

HRESULT __stdcall VF_ReadDataFunc_Blen( 
	VF_FileHandle hFileHandle,
	DWORD dwStream,void *lpData )
{
	char req[256];
	char buf[256];
	SOCKET s_in;
	int width;
	int height;
	int y;
	int rval;
	unsigned char * framebuf;

	conndesc * c = (conndesc*) hFileHandle;
	LPVF_ReadData_Video v = (LPVF_ReadData_Video) lpData;

	if (c == 0 || dwStream != VF_STREAM_VIDEO || v == 0) { 
		return VF_ERROR;
	}

	s_in = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s_in < 0) {
		return VF_ERROR;
	}

	if (connect(s_in, (struct sockaddr*) &c->addr,
		    sizeof(c->addr)) < 0) {
		goto errout;
	}

	sprintf(req, "GET /images/ppm/%d.ppm HTTP/1.0\n\n",
		(int) (v->dwFrameNumberL) + c->start);

	my_send(s_in, req);

	do {
		if (my_gets(s_in, buf, 256) <= 0) {
			goto errout;
		}
	} while (strcmp(buf, "P6\n") != 0);

	do {
		rval = my_gets(s_in, buf, 256);
	} while ( (buf[0] == '#' || buf[0] == '\n') && rval >= 0);

	if (sscanf(buf, "%d %d\n", &width, &height) != 2) {
		goto errout;
	}

	if (width != c->width || height != c->height) {
		goto errout;
	}

	my_gets(s_in, buf, 256); /* 255 */

	framebuf = (unsigned char *) v->lpData;

	for (y = 0; y < height; y++) {
		unsigned char *p = framebuf + v->lPitch * y;
		unsigned char *e = p + width * 3;

		my_recv(s_in, (char *)p, width * 3);
		while (p != e) {
			unsigned char tmp = p[2];
			p[2] = p[0];
			p[0] = tmp;

			p += 3;
		}
	}
	closesocket(s_in);
	return VF_OK;
 errout:
	closesocket(s_in);
	return VF_ERROR;
}

__declspec(dllexport) HRESULT vfGetPluginFunc( 
	LPVF_PluginFunc lpPluginFunc )
{
	if (!lpPluginFunc || lpPluginFunc->dwSize != sizeof(VF_PluginFunc)) {
		return VF_ERROR;
	}

	lpPluginFunc->OpenFile = VF_OpenFileFunc_Blen;
	lpPluginFunc->CloseFile = VF_CloseFileFunc_Blen;
	lpPluginFunc->GetFileInfo = VF_GetFileInfoFunc_Blen;
	lpPluginFunc->GetStreamInfo = VF_GetStreamInfoFunc_Blen;
	lpPluginFunc->ReadData = VF_ReadDataFunc_Blen;

	return VF_OK;
}
