/*
 * Softcam plugin to VDR (C++)
 *
 * This code 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 code 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 */

#include "stdafx.h"

#include <stdlib.h>
#include <fcntl.h>

#include <io.h>

#include "vdr/thread.h"
#include "vdr/tools.h"

#include "common.h"
#include "network.h"
#include "misc.h"

#define DEBUG_NET
//#define DEBUG_NET_DATA

#ifdef DEBUG_NET
#define dn(x) { (x); }
#else
#define dn(x) ; 
#endif

#ifdef DEBUG_NET_DATA
#define dnd(x) { x; }
#else
#define dnd(x) ; 
#endif

const char *netscript=0;
int netTimeout=60*1000;   // timeout for shutting down dialup-network

// -- cNetWatcher ---------------------------------------------------------------

class cNetWatcher : protected cThread {
private:
	int count;
	cTimeMs down;
	bool netup, downDelay;
	cSimpleList<cNetSocket> socks;
	WSADATA wsaData;
	//
	int RunCommand(const char *cmd, const char *state);
protected:
	virtual void Action(void);
public:
	cNetWatcher(void);
	~cNetWatcher();
	void Up(cNetSocket *so);
	void Down(cNetSocket *so);
	void Block(void) { Lock(); }
	void Unblock(void) { Unlock(); }
};

static cNetWatcher nw;

cNetWatcher::cNetWatcher(void)
:cThread("Netwatcher")
{
	count=0; netup=downDelay=false;
	if (WSAStartup(MAKEWORD(1,0), &wsaData))
		printf("WSAStartup error\n");
}

cNetWatcher::~cNetWatcher()
{
	Cancel(2);
	Lock();
	if(netup) RunCommand(netscript,"down");
	cNetSocket *so;
	while((so=socks.First())) socks.Del(so,false);
	Unlock();
}

void cNetWatcher::Up(cNetSocket *so)
{
	dn(printf("netwatch: up"))
//	Lock();
	socks.Add(so);
	if(!Active()) Start();
	if(netscript) {
		downDelay=false; netup=true;
		RunCommand(netscript,"up");
		count++;
		dn(printf(" (%d)\n",count))
	}
	else dn(printf("\n"))
//	Unlock();
}

void cNetWatcher::Down(cNetSocket *so)
{
	dn(printf("netwatch: down"))
//	Lock();
	socks.Del(so,false);
	if(netscript) {
		if(--count==0) {
			downDelay=true;
			down.Set(netTimeout);
		}
		dn(printf(" (%d)\n",count))
	}
	else dn(printf("\n"))
//	Unlock();
}

void cNetWatcher::Action(void)
{
	while(Running()) {
		SleepEx(1,false);
		Lock();
		if(downDelay && down.TimedOut()) {
			dn(printf("netwatch: netdown timeout expired\n"))
			if(netup) {
				RunCommand(netscript,"down");
				netup=false;
			}
			downDelay=false;
		}
		for(cNetSocket *so=socks.First(); so;) {
			cNetSocket *next=socks.Next(so);
			so->Lock();
			if(so->connected && so->activity.TimedOut()) {
				dn(printf("netwatch: idle timeout, disconnected %s:%d\n",so->hostname,so->port))
				so->Disconnect();
			}
			so->Unlock();
			so=next;
		}
		Unlock();
	}
}

int cNetWatcher::RunCommand(const char *cmd, const char *state)
{
	return 1;
}

// -- cNetSocket ------------------------------------------------------------------

cNetSocket::cNetSocket(int ConnectTimeout, int ReadWriteTimeout, int IdleTimeout, bool Udp)
{
	hostname=0; sd=-1; connected=netup=false;
	udp=Udp; conTimeout=ConnectTimeout; rwTimeout=ReadWriteTimeout;
	idleTimeout=IdleTimeout*1000;
}

cNetSocket::~cNetSocket()
{
	Disconnect();
	free(hostname);
}

void cNetSocket::Activity(void)
{
	activity.Set(idleTimeout);
}

bool cNetSocket::Connect(const char *Hostname, int Port, int timeout)
{
	nw.Block();
	Lock();
	Disconnect();
	if(Hostname) {
		free(hostname);
		hostname=_strdup(Hostname); port=Port;
	}
	if(timeout<0) timeout=conTimeout;

	nw.Up(this); netup=true;
	nw.Unblock();
	int proto=0;
	const struct protoent * const ptrp=getprotobyname(udp?"udp":"tcp");
	if(ptrp) proto=ptrp->p_proto;
	if((sd=socket(PF_INET,udp?SOCK_DGRAM:SOCK_STREAM,proto))>=0) {
		//int flgs=fcntl(sd,F_GETFL);
		//if(flgs>=0) {
			//if(fcntl(sd,F_SETFL,flgs|	)==0) {
				const struct hostent * const hostaddr=gethostbyname(hostname);
				if(hostaddr) {
					struct sockaddr_in socketAddr;
					socketAddr.sin_family=AF_INET;
					socketAddr.sin_port=htons(port);
					socketAddr.sin_addr.s_addr=((struct in_addr *)hostaddr->h_addr)->s_addr;
					dn(printf("socket: connecting to %s:%d/%s (%d.%d.%d.%d)\n",
								hostname,port,udp?"udp":"tcp",
								(socketAddr.sin_addr.s_addr>> 0)&0xff,(socketAddr.sin_addr.s_addr>> 8)&0xff,(socketAddr.sin_addr.s_addr>>16)&0xff,(socketAddr.sin_addr.s_addr>>24)&0xff))
					if(connect(sd,(struct sockaddr *)&socketAddr,sizeof(socketAddr))==0)
						connected=true;
					else if(errno==WSAEINPROGRESS) {
						if(Select(false,timeout)>0) {
							char r=-1;
							int l=sizeof(r);
							if(getsockopt(sd,SOL_SOCKET,SO_ERROR,&r,&l)==0) {
								if(r==0)
									connected=true;
								else d(printf("socket: connect failed (late): %s\n",strerror(r)))
							}
							else d(printf("socket: getsockopt failed: %s\n",strerror(errno)))
						}
						else d(printf("socket: connect timed out\n"))
					}
					else d(printf("socket: connect failed: %s\n",strerror(errno)))

					if(connected) { Activity(); Unlock(); return true; }
				}
				else d(printf("socket: name lookup error for %s\n",hostname))
			//}
			//else d(printf("socket: fcntl SETFL failed: %s\n",strerror(errno)))
		//}
		//else d(printf("socket: fcntl GETFL failed: %s\n",strerror(errno)))
	}
	else d(printf("socket: socket failed: %s\n",strerror(errno)))
	Unlock();
	Disconnect();
	return false;
}

void cNetSocket::Disconnect(void)
{
//	nw.Block();
//	cMutexLock lock(this);
	closesocket(sd); sd=-1; connected=false;
	if(netup) { nw.Down(this); netup=false; }
//	nw.Unblock();
}

void cNetSocket::Flush(void)
{
	cMutexLock lock(this);
	if(sd>=0) {
		unsigned char buff[512];
		while(recv(sd,(char*)buff,sizeof(buff),0)>0) Activity();
	}
}

int cNetSocket::Read(unsigned char *data, int len, int timeout)
{
	cMutexLock lock(this);
	if(timeout<0) timeout=rwTimeout;
	int r=Select(true,timeout);
	if(r>0) {
		r=recv(sd,(char*)data,len,0);
		if(r<0) dn(printf("socket: read failed: %s\n",strerror(errno)))
		else if(r>0) dnd(printf("socket: read "); HexDump(data,r))
	}
	Activity();
	return r;
}

int cNetSocket::Write(const unsigned char *data, int len, int timeout)
{
	cMutexLock lock(this);
	if(timeout<0) timeout=rwTimeout;
	int r=Select(false,timeout);
	if(r>0) {
		r=send(sd,(char*)data,len,0);
		if(r<0) dn(printf("socket: write failed: %s\n",strerror(errno)))
		else if(r>0) dnd(printf("socket: write "); HexDump(data,r))
    }
	Activity();
	return r;
}

int cNetSocket::Select(bool forRead, int timeout)
{
	if(sd>=0) {
		fd_set fds;
		FD_ZERO(&fds); FD_SET(sd,&fds);
		struct timeval tv;
		tv.tv_sec=timeout; tv.tv_usec=0;
		int r=select(sd+1,forRead ? &fds:0,forRead ? 0:&fds,0,&tv);
		if(r>0) return 1;
		else if(r<0) {
			dn(printf("socket: select failed: %s\n",strerror(errno)))
			return -1;
		}
		else {
			if(timeout>0) dn(printf("socket: select timed out (%d secs)\n",timeout))
			return 0;
		}
    }
	return -1;
}
