/*
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * Author:
 *         Stefan Denk <denk@ira.uka.de>
 *
 * $Id: local.c,v 1.6 2005/11/21 09:39:01 kutzner Exp $
 *
 */

#define PPM 1500

#include <unistd.h>
// for unlink()

typedef struct
{
	uint32_t hash;
	uint32_t len;
} cvideodata;

typedef struct
{
	string title;
	string channel;
} recinfo;

/** all things to be done with local recordings.
 * that is, files that are sitting on the local hard disk.
 *
 * this means: 
 *
 * - locating these recordings (entering them in a fitting data structure).
 * - parsing these recordings (extract GOPs + their hash values, the number of pictures and the offsets between the GOPs).
 * - adding this information to the cvideosources data structure.
 * 
 * in regard to the client and server, it may be sensible to integrate a routing like "where to start sending" in this class, too.
 */

class vdrrecordings
{
	private:
		list<vdrrecording*> vdrrecordings;
		/// iterate through all recordings ScanVideoDir has found at startup.
		int readrecordings(void);
		/// how many recfiles are sitting in one directory (one recording).
		int howmanyrecfiles(string recdir);
		/// add a recording to the list of recordings.
		void addrecording(string dir, int videofilesindir, cChannel *cChan, time_t starttime, time_t duration);
		/// retrieve information about a recording from the info.vdr file (Title, Channel fingerprint).
		int getrecinfo(string infofile, struct recinfo *result);
		/// build a channel object from the retrieved Channel fingerPrint (parsed from channels.conf).
		cChannel * buildchannel(string chanprint, vtchannel *vtc);
		/// parse a recording (all files in one directory) .
		void parse(string recdir, int recfilecount);
		/// from the parsed data, create one minute blocks to insert into cvideosources.
		void buildminuteblocks(string recdir, int recfilecount);
	public:
		/// retrieve the position where the server has to start sending (will start at beginning position of hash1).
		int startsending(uint32_t hash1, uint32_t hash2, uint32_t hash3, fileandpos * result);	
};

/** read all the recordings stored on disk (that is, all recordings ScanVideoDir could find).
 * \param none.
 * \return the number of recordings found or -1 on error.
 */

int vdrrecordings::readrecordings(void)
{
	cRecording *rec;
	string infofile;
	int recfilecount;
	recinfo myrecinfo;
	cChannel chan;
	unsigned long starttime;
	
	recinfo *result = myrecinfo;
	vtchannel *vtc;
	cChannel *cChan;

	// for each recording
	for (rec = Recordings.First(); rec != Recordings.Last(); rec = Recordings.Next())
	{
		// count the number of recfiles in this recording (directory)
		recfilecount = howmanyrecfiles(rec->FileName());
		// locate the info file
		infofile = string(rec->FileName()) + "/info.vdr";
		// parse channel and title information from the info file
		getrecinfo(infofile, result);
		// use this information to build a cChannel object
		cChan = buildchannel(myrecinfo.channel, vtchannel *vtc);
		// parse this recording (directory) for video data
		parse(rec->FileName(), recfilecount);
		// fetch the start time of the recording
		starttime = getrectime(rec->FileName());
		// add this recording to the list of recordings
		addrecording(rec->FileName(), recfilecount, cChan, starttime, 0);
		// build minute blocks out of the parsed data files and publish them to cvideosource
		buildminuteblocks(rec->FileName(), recfilecount, starttime, cChan);
	}
}

/** check how many consecutive recfiles holding video data can be found in one recording (directory).
 * \param string dir: directory to search.
 * \return number of files found / TOOMANYRECFILESFOUND if files found > 999.
 */

int vdrrecordings::howmanyrecfiles(string recdir)
{
	struct stat st_rec;
	char infofile[MAXPATHLENGTH];
	int i;
	for (i = 0 ; i <= 1000; i++){
		snprintf(infofile, MAXPATHLENGTH, "%s%s%03i%s", recdir.c_str(), "/", i, ".vdr");
		if (stat(infofile, &st_rec) == -1)
			break;
	}
	if (i==1000)
		return TOOMANYRECFILESFOUND;
	return i-1;
}

void vdrrecordings::addrecording(string dir, int videofilesindir, cChannel *cChan, time_t starttime, time_t duration)
{
        dsyslog("%s: begin", __PRETTY_FUNCTION__);
        vdrrecordings.push_back(new vdrrecording(dir, videofilesindir, channel, starttime, duration));
}

/** parses the file info.vdr created by vdr for the channel (tChannelID) and title of the recording.
 * \param string infofile: path to (and including) info.vdr.
 * \param struct recinfo *result: the struct storing the channel and the title.
 * \return 0 on success, -1 on error (no info.vdr found).
 */

int vdrrecordings::getrecinfo(string infofile, struct recinfo *result)
{
        FILE *infofp;
        char *line = NULL;
        size_t len = 0;
        int pos;

	// open the infofile for reading; if this fails, an "empty" result set is returned
        infofp = fopen(infofile.c_str(), "r");
        if (infofp == NULL)
        {
                return -1;
        }
	// parse the lines of infofile; so far only the channel and the title are of interest
        while ((read = getline(&line, &len, infofp)) != -1)
        {
                switch (line[0])
		case 'C': result->channel = line; break;
		case 'T': result->title = line; break;
		default: break;
        }
        if (line) free(line);
        fclose(infofp);
	// remove newlines from the result strings
        pos = result->channel.rfind("\n");
        result->channel = result->channel.substr(2,pos-2);
        pos = result->title.rfind("\n");
        result->title = result->title.substr(2,pos-2);
	// replace blanks by underscores
	pos = result->title.find(' ');
        while(pos != -1)
        {
                result->title[pos] = '_';
                pos = result->title.find(' ');
        }
        return 0;
}

/** parse the start time of the recording from the file system.
 * \param string dir: path to the recording.
 * \return unixtime, in minutes.
 */

unsigned long vdrrecordings::getrectime(string dir)
{
	string stringtime;
	unsigned long unixtime;
	char unixtimebuf[20];
	struct tm tm;
	
	stringtime = dir.substr(dir.size()-26, dir.size()-7);	///\bug check for off-by-ones!
	strptime(stringtime.c_str(), "%Y-%m-%d.%H:%M.%S", &tm);
	strftime(unixtimebuf, sizeof(unixtimebuf), "%s", &tm);
	unixtime = strtol(unixtimebuf, NULL, 10);
	return unixtime / 60;
}

/** build a vtchannel object from the channel information gathered from the info.vdr file.
 * \param vtc: new instance of vtchannel.
 * \return true.
 */

cChannel * buildchannel(string chanprint, vtchannel *vtc)
{
	vtc = new vtchannel(&chanprint);
	return vtchannel.tovdr();
}

/** parse all recfiles holding video data in one recording (directory) for metadata.
 * \param string recdir: the path to the recording (directory).
 * \param int recfilecount: the number of recfiles found previously in that directory.
 * note: even on slow systems with lots of recordings this task WILL eventually terminate.
 */

void vdrrecordings::parse(string recdir, int recfilecount)
{
	char recfile[MAXPATHLENGTH];
	char infofile[MAXPATHLENGTH];
	char *rec = recfile;
	char *info = infofile;
	int errorcounter = 0;
	struct stat vidstat;
	struct stat recstat;

	// for each recfile in this directory
	for( int i = 1; i++; i <= recfilecount )
	{
		// build the names of the current recfile (source) and viddata file (target) 
		snprintf(recfile, MAXPATHLENGTH, "%s%s%03i%s", recdir.c_str(), "/", i, ".vdr");
		snprintf(infofile, MAXPATHLENGTH, "%s%s%03i", recdir.c_str(), "/viddata.", i);
		bzero(&recstat, sizeof(recstat));
		bzero(&vidstat, sizeof(vidstat));
		stat(recfile, &recstat);
		// if the viddata file does not yet exist or if the recfile was changed since the last parsing (the first test for -1 should be redundant)
		if ( (stat(infofile, &vidstat) == -1) || (recstat.st_mtime > vidstat.st_mtime) )
		{
			// parse the current recfile, write output to infofile; give it up to three tries in case of errors
			if ( parsefile(recfile, infofile) == -1 && errorcounter < 3)
			{
				esyslog("something went wrong while parsing file %s, trying again.", recfile);
				unlink(info);
				errorcounter++;
				i--;
			}
			// all went smoothly with this set; reset errorcounter
			else
			{
				errorcounter = 0;
			}
		}
	// and parse next file
	}
}

typedef vector<video_hash> hashes_t;
typedef list <video_hash*> many_minutes_t;

/** build ~1 min blocks (that is, vectors) of hash and len values taken from the viddata files and publish them.
 * \param string recdir the directory that is currently processed.
 * \param int recfilecount how many viddata files are expected.
 * \param unsigned long starttime when the recording started.
 * \param cChannel * cChan the cChannel object.
 * \return no so far, but 0 on success and -1 on error would probably be nice.
 */

void vdrrecordings::buildminuteblocks(string recdir, int recfilecount, unsigned long starttime, cChannel * cChan)
{
	char infofile[MAXPATHLENGTH];
	int vidfp;
	struct stat st;
	int viddataentries = 0;
	int lastmodulo = 0;
	hashes_t oneminhashlist;
	many_minutes_t minutelist;
	
	// for each viddatafile in this directory
	for( int i = 1; i <= recfilecount; i++ )
	{
		// build the names of the current viddata file (source) 
		snprintf(infofile, MAXPATHLENGTH, "%s%s%03i", recdir.c_str(), "/viddata.", i);
		vidfp = open(infofile, O_RDONLY);
		bzero(&st, sizeof(st));
		fstat(vidfp, &st);
		viddata = (vid*) mmap(0, filesize, PROT_READ, MAP_PRIVATE, vidfp, 0);
		viddataentries = st.st_size / sizeof(viddata);
		for( int j = 0; j < viddataentries; j++ )
		{
			// build vector
			video_hash vh(viddata[j].hash, viddata[j].len);
			oneminhashlist.push_back(vh);
			lastmodulo = viddata[j].picno % PPM;
			// test if one minute (1500 pics per minute) is reached, if yes publish vector, flush and restart
			if ( lastmodulo > viddata[j].picno % PPM)
			{
				// generate ~10% overlap to next cvideosource (if there are enough entries left in that viddata file)
				for (int k = 1; k <= 12; k++)
				{
					if (j+k < viddataentries)
					{
						video_hash vh(viddata[j+k].hash, viddata[j+k].len);
						oneminhashlist.push_back(vh);
					}
				}
				aggr_dest ad(starttime, cChan);
				publish(ad, oneminhashlist);	///\bug implement publish() - or better fit it in cvideosources?
				oneminhashlist.erase();
				starttime++;
			}
		}
		// throw away the last few seconds of videodata (only full minutes get published)
		oneminhashlist.erase();
		starttime++;
		munmap(viddata, filesize);
		close(vidfp);
	}
}

/** locates the position of three consecutive hash blocks in the viddata files generated by parsefile
 * \param uint32_t hash1-3: the three hash values
 * \param fileandpos * result: pointer to a fileandpos structure to store the result
 * \return 0 on success, -1 if the hash sequence is not found and -2 if there is no infofile (info.vdr) available to parse neccessary data
 */

int vdrrecordings::startsending(uint32_t hash1, uint32_t hash2, uint32_t hash3, fileandpos * result)
{
	int vidfp;
        int counter = 0;
        uint32_t finaloffset = 0;
        int viddataentries = 0;
        ssize_t filesize = 0;
        struct stat fileinfo;
        string path, infofile;
        vid * viddata;
        bool foundfirsthash = false;
        bool foundsecondhash = false;
        bool foundthirdhash = false;
        int files = 0;
	int whichfile = 0;
	char buffer[MAXPATHLENGTH];
	
        recinfo myrecinfo;
        recinfo *recinfo = myrecinfo;
 
        vdrrecordings::const_iterator current = get_recordingdata().begin();
        vdrrecordings::const_iterator last = get_recordingdata().end();

        for(; current != last; current++)
        {
                path = (**current).get_dir();
                files = (**current).get_videofilesindir();

                for(int i = 1; i <= files; i++)
                {
                        videodata.clear();
			snprintf(buffer, MAXPATHLENGTH, "%s%s%03i", path.c_str(), "/viddata.", whichfile);
                        bzero(&fileinfo, sizeof(fileinfo));
                        vidfp = open(buffer, O_RDONLY);
                        fstat(vidfp, &fileinfo);
                        filesize = fileinfo.st_size;
                        viddataentries = filesize / sizeof(vid);

                        viddata = (vid*) mmap(0, filesize, PROT_READ, MAP_PRIVATE, vidfp, 0);

                        for (counter = 0; counter < viddataentries; counter++)
                        {
                                if(viddata[counter].hash == hash1) 
				{
					foundfirsthash = true;
					whichfile = i;	// remember in which file the first hash was found, as the triple may extend over it
				}
                                if(viddata[counter].hash == hash2 && foundfirsthash == true) foundsecondhash = true;
                                if(viddata[counter].hash == hash3 && foundfirsthash == true && foundsecondhash == true) foundthirdhash = true;
                                if(viddata[counter].hash != hash1 && viddata[counter].hash != hash2 && viddata[counter].hash != hash3)
                                {
                                        foundfirsthash = false;
                                        foundsecondhash = false;
                                        foundthirdhash = false;
                                }

                                if(foundthirdhash == true)      // success: we have found the hash triple
                                {
                                        finaloffset = viddata[counter-2].offset;
                                        printf("found hash triple in vdr.%03d, starting transmission at %u\n", whichfile, finaloffset);
                                        counter = 0;
                                        close(vidfp);
                                        munmap(0, filesize);
                                        infofile = path + "/info.vdr";

                                        if ( getrecinfo(infofile, recinfo) == 0)
					{
	                                        result->channel = recinfo->channel;
        	                                result->title = recinfo->title;
                	                        result->position = finaloffset;
						snprintf(buffer, MAXPATHLENGTH, "%s%s%03i%s", path.c_str(), "/", whichfile, ".vdr");
                        	                result->path = buffer;	///\bug change fileandpos to prevent that double action
                                	        return 0;
					}
					else return -2;
                                }
                        }

                        munmap(0, filesize);
                        close(vidfp);
                }
        }
        close(vidfp);
        return -1;
}
 
