#include "Cache.h" // first for lseek64 support

#include "AsfFileInputStream.h"
#include "FileIterator.h"
#include "asf_guids.h"

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifndef O_LARGEFILE
#define O_LARGEFILE 0
#endif

#define __MODULE__ "AsfFileInputStream"

using namespace std;

/* first, we make a completely stupid implementation */

ASFFileInputStream::ASFFileInputStream(const char* pszFile)
{
    pthread_mutex_init(&m_Mutex, 0);
    maxStreamId = 0;
    m_iFd = open(pszFile, O_RDONLY | O_LARGEFILE);
    if (m_iFd<0)
	throw FATAL("Could not open the file");
    bool have_header=false;
    bool have_data=false;
    ASFStreamHeader strhdr;
    while (1)
    {
	GUID guid;
	int64_t size;
	if (!read(m_iFd, &guid, 16))break;
	if (!read(m_iFd, &size, 8))break;
	if (size<24)break;
	size -= 24;
	switch (LookupChunkType(guid))
	{
	case 0: //unknown chunk type
	default:
	    lseek64(m_iFd, size, SEEK_CUR);
	    continue;
	case ASF_CHUNK_FILE_HEADER:
	    if (size<sizeof(MainASFHeader))
		throw FATAL("Wrong ASF header size");
	    if (size>sizeof(MainASFHeader))
		cout<<"Warning: unexpected size of ASF header "<<(double)size<<endl;
	    read(m_iFd, &m_Header, sizeof(MainASFHeader));
	    lseek64(m_iFd, size-sizeof(MainASFHeader), SEEK_CUR);
	    have_header=true;
	    continue;
	case ASF_CHUNK_DATA:
	    if (size<26)
		throw FATAL("Wrong data chunk size");
	    m_lDataOffset = lseek64(m_iFd, 0, SEEK_CUR)+26;
	    lseek64(m_iFd, size, SEEK_CUR);
	    have_data = true;
	    continue;
	case ASF_CHUNK_STREAM_HEADER:
	    if (size > sizeof(strhdr))
		cout<<"Warning: unexpected size of ASF stream header "<<(double)size<<endl;
	    read(m_iFd, &strhdr, (size<sizeof(strhdr))?size:sizeof(strhdr));
            cout << "STREAM_HEADER  " << strhdr.hdr.stream << endl;
	    if (strhdr.hdr.stream > maxStreamId)
		maxStreamId = strhdr.hdr.stream;
	    m_Streams.push_back(strhdr);
	    if (size > sizeof(strhdr))
		lseek64(m_iFd, size-sizeof(strhdr), SEEK_CUR);
	    continue;
	case ASF_CHUNK_HEADER:
	    lseek64(m_iFd, 6, SEEK_CUR);
	    continue;
	}
    }
    if(!have_header)
	throw FATAL("Could not find header chunk in file");
    if(!have_data)
	throw FATAL("Could not find data chunk in file");
    createSeekData();
}

ASFFileInputStream::~ASFFileInputStream()
{
    if (m_iFd >= 0)
	close(m_iFd);
    pthread_mutex_destroy(&m_Mutex);
}


void ASFFileInputStream::createSeekData()
{
    unsigned int last_start[m_Streams.size()];
    int convertTable[maxStreamId + 1];
    memset(convertTable, maxStreamId + 1, sizeof(int) * (maxStreamId + 1));

    for (unsigned l = 0; l < m_Streams.size(); l++)
    {
	m_SeekInfo.push_back(new ASFStreamSeekInfo);
	last_start[l] = 0;
	//cout << "index " << m_Streams[l].hdr.stream << "  " << l << endl;
        convertTable[m_Streams[l].hdr.stream] = l;
    }
    cout<<"Creating seek data, please wait..."<<endl;
    Iterator* it = getIterator(0);//argument only needed in seek operations
    bool success = true;
    unsigned i;
    for (i = 0; i < m_Header.num_packets; i++)
    {
	packet p = it->getPacket(success);
	if (!success)
	    break;
	for (unsigned j = 0; j < p.fragments.size(); j++)
	{
	    const fragment& f = p.fragments[j];
	    chunk_info ch;
	    if (f.object_length != f.data_length && f.fragment_offset)
		continue;

	    int stream_id = (f.stream_id <= maxStreamId)
		? convertTable[f.stream_id] : maxStreamId + 1;

	    if (stream_id > maxStreamId )
	    {
		cout<<"WARNING: Unexpected stream_id ( packet "<<i
		    <<", send time "<<p.hdr.send_time/1000.<<", fragment "
		    <<j<<", stream_id "<<(int)f.stream_id<<" )"<<endl;
		continue;
	    }
	    ch.object_start_time = f.object_start_time-m_Header.play_start_time;
            ch.object_length = f.object_length;
	    if (last_start[stream_id] > 0)
	    {
		if (ch.object_start_time < last_start[stream_id])
		{
		    cout<<"WARNING: Negative send time difference ( packet "<<i
			<<", packet send time "<<p.hdr.send_time/1000.<<", fragment "
			<<j<<", stream_id "<<(int)f.stream_id
			<<", fragment send time "<<ch.object_start_time/1000.
			<<", last fragment send time "
			<<m_SeekInfo[stream_id]->back().object_start_time/1000.
			<<" )"<<endl;
		    continue;
		}
	    }
	    ch.keyframe=f.keyframe;
	    ch.packet_id=i;
	    ch.fragment_id=j;
            last_start[stream_id] = ch.object_start_time;
	    m_SeekInfo[stream_id]->push_back(ch);
	}
	it->next();
    }
#if 0
    ASFStreamSeekInfo* si = m_SeekInfo[1];
    for (unsigned i = 0; i < si->size(); i++)
    {
	chunk_info& c = si->operator[](i);

	cout << i << "   " << c.packet_id << "   " << c.object_start_time << "  "
	    << (int)c.keyframe << endl;
    }
#endif

    m_Header.num_packets = i;
    cout << "Seek data created ( processed " << i << " packets )" << endl;
    it->release();
}

Iterator* ASFFileInputStream::getIterator(int id)
{
    cout << "GETITERATOR " << id << endl;
    return new FileIterator(this, id);
}

void ASFFileInputStream::interrupt()
{
}

double ASFFileInputStream::cacheSize() const
{
    return 1.;
}

void ASFFileInputStream::clear()
{
}
