// ------------------------------------------------
// File : channel.h
// Date: 4-apr-2002
// Author: giles
//
// (c) 2002 peercast.org
// ------------------------------------------------
// 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.
// ------------------------------------------------

#ifndef _CHANNEL_H
#define _CHANNEL_H

#include "sys.h"
#include "stream.h"
#include "gnutella.h"
#include "xml.h"

// --------------------------------------------------
struct MP3Header
{
	int lay;
	int version;
	int error_protection;
	int bitrate_index;
	int sampling_frequency;
	int padding;
	int extension;
	int mode;
	int mode_ext;
	int copyright;
	int original;
	int emphasis;
	int stereo;
};
// ----------------------------------
class OggPacket
{
public:
	enum {
		MAX_BODYLEN = 65536,
		MAX_HEADERLEN = 27+256
	};

	void	read(Stream &);
	bool	isBOS();
	bool	isNewPacket();

	int headLen,bodyLen;
	unsigned char data[MAX_HEADERLEN+MAX_BODYLEN];
};
// ----------------------------------
class VorbisPacket
{
public:
	enum {
		MAX_BODYLEN = 65536		// probably too small
	};


	int	bodyLen;
	unsigned char body[MAX_BODYLEN];
};

// ----------------------------------
class TrackInfo
{
public:
	void	clear()
	{
		contact.clear();
		title.clear();
		artist.clear();
		album.clear();
		genre.clear();
	}

	void	convertTo(String::TYPE t)
	{
		contact.convertTo(t);
		title.convertTo(t);
		artist.convertTo(t);
		album.convertTo(t);
		genre.convertTo(t);
	}
	::String	contact,title,artist,album,genre;
};



// ----------------------------------
class ChanInfo
{
public:
	enum TYPE
	{
		T_UNKNOWN,

		T_RAW,
		T_MP3,
		T_OGG,
		T_MOV,
		T_MPG,
		T_NSV,

		T_WMA,
		T_WMV,

		T_PLS,
		T_ASX
	};


	enum PROTOCOL
	{
		SP_UNKNOWN,
		SP_PEERCAST,
		SP_HTTP,
		SP_FILE,
		SP_MMS
	};


	enum STATUS
	{
		S_UNKNOWN,
		S_PLAY
	};

	ChanInfo() {init();}

	void	init();
	void	init(const char *);
	void	init(const char *, GnuID &, TYPE, int);
	void	init(XML::Node *);
	void	initNameID(const char *);

	void	updateFromXML(XML::Node *);

	void	readTrackXML(XML::Node *);
	void	readServentXML(XML::Node *);
	void	update(ChanInfo &);
	XML::Node *createQueryXML();
	XML::Node *createChannelXML();
	XML::Node *createRelayChannelXML();
	XML::Node *createTrackXML();
	bool	match(XML::Node *);
	bool	match(ChanInfo &);

	unsigned int getUptime();
	bool	isActive() {return id.isSet();}
	static	const	char *getTypeStr(TYPE);
	static	const	char *getProtocolStr(PROTOCOL);
	static	const	char *getTypeExt(TYPE);
	static	TYPE		getTypeFromStr(const char *str);
	static	PROTOCOL	getProtocolFromStr(const char *str);

	::String	name;
	GnuID	id;
	int		bitrate;
	TYPE	contentType;
	PROTOCOL	srcProtocol;
	unsigned int lastPlay;
	unsigned int numSkips;

	STATUS  status;

	TrackInfo	track;
	::String	desc,genre,url,comment;
};


// ----------------------------------
class ChanHit
{
public:
	void	init();
	XML::Node *createXML();

	Host	host;
	int		numListeners;
	bool	firewalled,busy,stable;
	int		index;
	int		hops;
	int		numSkips;
	unsigned int time,upTime;
	char	agentStr[16];
	int		maxPreviewTime;
	GnuID	packetID;
};
// ----------------------------------
class ChanHitList
{
public:
	enum {
		MAX_HITS = 200
	};
	void	init();

	ChanHit	*addHit(ChanHit &);
	void	deadHit(ChanHit &);
	int		numHits();
	int		numListeners();
	int		numBusy();
	int		numStable();
	int		numFirewalled();
	int		closestHit();
	int		furthestHit();
	unsigned int		newestHit();

	ChanHit	getHit(bool);
	bool	isUsed() {return info.isActive();}
	bool	isAvailable();
	int		clearDeadHits(unsigned int);
	XML::Node *createHitsXML();

	ChanInfo	info;
	ChanHit	hits[MAX_HITS];
	unsigned int lastHitTime;
	int index;
	bool	locked;

};
// ----------------------------------
class ChanPacket
{
public:
	enum 
	{
		MAX_DATALEN = 16384
	};

	void	init(unsigned int t, const void *p, unsigned int l);
	void	write(Stream &);
	void	read(Stream &);


	unsigned int type;
	unsigned int len;
	char data[MAX_DATALEN];
};
// ----------------------------------
class ChanPacketBuffer 
{
public:
	enum {
		MAX_PACKETS = 64
	};

	void	init() {currPacket = lastPacket = firstPacket = 0;}

	unsigned int	writePacket(ChanPacket &);
	unsigned int	readPacket(unsigned int,ChanPacket &);

	ChanPacket	packets[MAX_PACKETS];
	unsigned int currPacket,lastPacket,firstPacket;
	WLock lock;
};

#if 0
// ----------------------------------
class ChanBuffer 
{
public:
	enum {
		MAX_DATALEN = 65536
	};

	void init() {pos=lastPacket=firstPacket=0;}
	unsigned int read(unsigned int,void *,unsigned int);
	void	write(const void *,int);

	void	writePacket(ChanPacket &);
	unsigned int	readPacket(unsigned int,ChanPacket &);


	unsigned int pos,lastPacket,firstPacket;
	char data[MAX_DATALEN];

	WLock	lock;
};
#endif

// ----------------------------------
class ChanMeta
{
public:
	enum {
		MAX_DATALEN = 65536
	};

	void	init()
	{
		len = 0;
		cnt = 0;
		startPos = 0;
	}

	void	fromXML(XML &);
	void	fromMem(void *,int);
	void	addMem(void *,int);

	unsigned int len,cnt,startPos;
	char	data[MAX_DATALEN];
};

// ----------------------------------
class Channel
{
public:
	
	enum STATUS
	{
		S_NONE,
		S_WAIT,
		S_CONNECTING,
		S_REQUESTING,
		S_CLOSING,
		S_RECEIVING,
		S_BROADCASTING,
		S_ABORT,
		S_SEARCHING,
		S_NOHOSTS,
		S_IDLE,
		S_ERROR
	};

	enum TYPE
	{
		T_NONE,
		T_ALLOCATED,
		T_BROADCAST,
		T_RELAY
	};

	enum SRC_TYPE
	{
		SRC_NONE,
		SRC_PEERCAST,
		SRC_SHOUTCAST,
		SRC_ICECAST,
		SRC_URL
	};


	void	init();
	void	reset();
	void	close();
	void	endThread();

	void	startMP3File(char *);
	void	startGet();
	void	startICY(ClientSocket *,SRC_TYPE);
	void	startFind();
	void	startURL(const char *);

	void	resetPlayTime();
	bool	isPlaying()
	{
		return (status == S_RECEIVING) || (status == S_BROADCASTING);
	}

	bool	isReceiving()
	{
		return (status == S_RECEIVING);
	}

	bool	isBroadcasting()
	{
		return (status == S_BROADCASTING);
	}

	bool	isFull();

	bool	checkBump()
	{
		if (bump)
		{
			bump = false;
			return true;
		}else
			return false;
	}

	bool	checkIdle();

	bool	isActive()
	{
		return type != T_NONE;
	}

	bool	isIdle() {return isActive() && (status==S_IDLE);}

	static THREAD_PROC	streamMP3File(ThreadInfo *);
	static THREAD_PROC	streamGet(ThreadInfo *);
	static THREAD_PROC	streamICY(ThreadInfo *);
	static THREAD_PROC	streamURL(ThreadInfo *);
	static THREAD_PROC	findProc(ThreadInfo *);

	::String	streamURL(const char *);

	void	setStatus(STATUS s);
	const char  *getSrcTypeStr() {return srcTypes[srcType];}
	const char	*getStatusStr() {return statusMsgs[status];}
	const char	*getName() {return info.name.cstr();}
	GnuID	getID() {return info.id;}
	int		getBitrate() {return info.bitrate; }
	void	getIDStr(char *s) {info.id.toStr(s);}
	void	getStreamPath(char *);

	void	updateMeta();

	void	readStream();
	void	readOGG();
	void	readVorbisHeaders(Stream &,OggPacket &);
	void	readMP3();
	void	readRaw();
	void	readPeercast();
	void	readMOV();
	void	readMPG();
	void	readMMS();

	void	checkReadDelay(unsigned int);

	void	processMetadata(char *);

	void	readVorbisComment(Stream &);
	void	readVorbisIdent(Stream &);
	void	readVorbisSetup(Stream &);

	void	readHeader();
	void	readOggHeader();

	XML::Node *createRelayXML(bool);


	void	giveSocket(ClientSocket *s)
	{
		if (!pushSock)
			pushSock = s;
	}

	::String mount;
	ChanMeta	headMeta,insertMeta;
	ChanPacketBuffer	chanData;
	

	int		numRelays,numListeners;

	ChanInfo	info;
	ChanHit		currSource;

	::String  sourceURL;

	bool	bump,stayConnected;
	int		icyMetaInterval;
	unsigned int syncPos;
	bool	readDelay;

	TYPE	type;
	Stream	*input;

	SRC_TYPE	srcType;

	MP3Header mp3Head;
	ThreadInfo	thread;

	unsigned int lastIdleTime,prefetchCnt;
	int		index;
	int		status;
	static	char *statusMsgs[],*srcTypes[];

	ClientSocket	*sock;
	ClientSocket	*pushSock;
	int		pushIndex;
};

// ----------------------------------
class ChanMgr
{
public:
	enum {
		MAX_CHANNELS = 16,
		MAX_IDLE = 8,
		MAX_HITLISTS = 200
	};


	ChanMgr();

	Channel	*createChannel(ChanInfo &,const char *);
	Channel *findChannelByName(const char *);
	Channel *findChannelByIndex(int);
	Channel *findChannelByMount(const char *);
	Channel *findChannelByID(GnuID &);
	Channel *findPushChannel(int);
	Channel	*findChannel(ChanInfo &);
	Channel	*findListenerChannel();

	void	broadcastRelays(Servent *,int,int);

	int		findChannels(ChanInfo &,Channel **,int);
	int		findChannelsByStatus(Channel **,int,Channel::STATUS);

	int		numConnected();
	int		numRelayed();
	int		numListeners();
	int		numIdle();

	unsigned int		totalInput();
	ChanHit	*addHit(ChanInfo &, ChanHit &);
	void	setFirewalled(Host &);

	ChanHitList *findHitList(ChanInfo &);
	ChanHitList *findHitListByID(GnuID &id);
	ChanHitList	*addHitList(ChanInfo &);
	void		clearHitLists();
	void		clearDeadHits();
	int			numHitLists();
	void		lockHitList(GnuID &,bool);

	void		setBroadcastMsg(::String &);

	Channel		*createRelay(ChanInfo &,bool);
	int			findAndRelay(ChanInfo &,Channel **,int);
	void		startSearch(ChanInfo &);

	void		playChannels(Channel **,int);
	void		playChannel(GnuID &,bool);

	Channel channels[MAX_CHANNELS];
	ChanHitList	hitlists[MAX_HITLISTS];

	GnuID	broadcastID;

	ChanInfo	searchInfo;

	int		numFinds;
	::String	broadcastMsg;
	unsigned int		broadcastMsgInterval;
	unsigned int lastHit,lastQuery;
	unsigned int maxUptime;
	bool	searchActive;
	unsigned int		deadHitAge;
	int		icyMetaInterval;
	int		maxStreamsPerChannel;
	WLock	lock;
	int		minBroadcastTTL,maxBroadcastTTL;
	int		pushTimeout,pushTries,maxPushHops;
	unsigned int		autoQuery;

};
// ----------------------------------
class PlayList
{
public:

	enum TYPE
	{
		T_NONE,
		T_SCPLS,
		T_PLS,
		T_ASX
	};

	PlayList(TYPE t, int max)
	{
		maxURLs = max;
		numURLs = 0;
		type = t;
		urls = new ::String[max];
		titles = new ::String[max];
	}

	~PlayList()
	{
		delete [] urls;
		delete [] titles;
	}

	void	addURL(const char *url, const char *tit)
	{
		if (numURLs < maxURLs)
		{
			urls[numURLs].set(url);
			titles[numURLs].set(tit);
			numURLs++;
		}
	}
	void	addChannels(const char *,Channel **,int);
	void	addChannel(const char *,ChanInfo &);

	void	writeSCPLS(Stream &);
	void	writePLS(Stream &);
	void	writeASX(Stream &);

	void	readSCPLS(Stream &);
	void	readPLS(Stream &);
	void	readASX(Stream &);

	void	read(Stream &s)
	{
		try
		{
			switch (type)
			{
				case T_SCPLS: readSCPLS(s); break;
				case T_PLS: readPLS(s); break;
				case T_ASX: readASX(s); break;
			}
		}catch(StreamException &) {}	// keep pls regardless of errors (eof isn`t handled properly in sockets)
	}

	void	write(Stream &s)
	{
		switch (type)
		{
			case T_SCPLS: writeSCPLS(s); break;
			case T_PLS: writePLS(s); break;
			case T_ASX: writeASX(s); break;
		}
	}

	TYPE	type;
	int		numURLs,maxURLs;
	::String	*urls,*titles;
};

// ----------------------------------

extern ChanMgr *chanMgr;


#endif
