/***************************************************************************
                           csharelist.cpp  -  description
                             -------------------
    begin                : Mon May 12 2003
    copyright            : (C) 2003-2005 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "csharelist.h"

#ifndef WIN32
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>

#include "core/che3.h"
#include "core/cbz.h"
#include "core/cdir.h"
#include "core/cxml.h"
#include "core/cbytearray.h"
#include "cconfig.h"
#include "csearchindex.h"

// for encoding of non-xml lists which no-one should still be using...
#include "core/ciconv.h"

// for DC_USER_INDEXLIST
#include "dcobject.h"

#include "csharetreefolder.h"

/** */
CShareList::CShareList()
{
	m_nShareSize        = 0;
	m_pHE3ShareBuffer   = 0;
	m_pBZShareBuffer    = 0;
	m_pXMLBZShareBuffer = 0;
	m_pRootItem = 0;
}

/** */
CShareList::~CShareList()
{
	m_MutexShareList.Lock();

	delete m_pHE3ShareBuffer;
	m_pHE3ShareBuffer = 0;

	delete m_pBZShareBuffer;
	m_pBZShareBuffer = 0;

	delete m_pXMLBZShareBuffer;
	m_pXMLBZShareBuffer = 0;
	
	delete m_pRootItem;
	m_pRootItem = 0;
	
	m_MutexShareList.UnLock();
}

/** */
bool CShareList::Load()
{
	bool res = false;
	CString s;
	CByteArray ba;

	m_MutexShareList.Lock();
	
	s = CConfig::Instance()->GetConfigPath() + DC_USER_INDEXLIST;

	if ( CDir().getFileSize(s,false) > 0 )
	{
		if ( ba.LoadFromFile(s) )
		{
			m_sIndexShareBuffer.Set((const char*)ba.Data(),ba.Size());
			res = true;
		}
	}
	
	m_MutexShareList.UnLock();
	
	return res;
}

/** */
void CShareList::Save()
{
	FILE * out;
	CString s;

	m_MutexShareList.Lock();

	s = CConfig::Instance()->GetConfigPath() + DC_USER_INDEXLIST;

	if ( (out = fopen(s.Data(),"wb")) != 0 )
	{
		// fwrite returns the number of elements written, not number of characters
		if ( fwrite( m_sIndexShareBuffer.Data(), m_sIndexShareBuffer.Length(), 1, out ) != 1 )
		{
			if ( m_sIndexShareBuffer.NotEmpty() )
			{
				printf("CShareList::Save: error writing %s\n",s.Data());
			}
		}

		fclose(out);
	}
	else
	{
		printf("[ERROR] open %s\n",s.Data());
	}
	
	m_MutexShareList.UnLock();
}

/** */
void CShareList::SetIndexBuffer( const CString & s )
{
	m_MutexShareList.Lock();
	
	m_sIndexShareBuffer = s;
	
	m_MutexShareList.UnLock();
	
	// save share list
	Save();
}

/** */
void CShareList::CreateList( CSearchIndex * si )
{
	CString s,s1;
	long i,j,d;
	ulonglong sharesize;
	unsigned long index,oindex;
	CString res,resnew;
	struct filebaseobject fbo;
	int den,deo,dde;
	CXml xml;
	
	m_MutexShareList.Lock();
	
	delete m_pRootItem;
	m_pRootItem = new CShareTreeFolder( CString('/'), 0 );
	CShareTreeFolder * current = m_pRootItem;
	
	delete m_pHE3ShareBuffer;
	m_pHE3ShareBuffer = 0;

	delete m_pBZShareBuffer;
	m_pBZShareBuffer = 0;

	delete m_pXMLBZShareBuffer;
	m_pXMLBZShareBuffer = 0;
	
	resnew  = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n";
	resnew += "<FileListing Version=\"1\" Generator=\"dclib ";
	resnew += DCLIB_VERSION_STRING;
	/* FIXME CID="base32_encoded_data" is required! */
	resnew += "\" Base=\"/\">\n";
	
	sharesize = 0;
	i = j = 0;
	CString fi="\xD\xA";
	den = 0;
	deo = -1;
	dde = 0;
	oindex = 0;
	
	while((i=m_sIndexShareBuffer.Find(fi,j))>0)
	{
		s1 = m_sIndexShareBuffer.Mid(j,i-j);

		d = s1.FindRev('|');
		
		if ( (d != -1) &&
		     (s1.Length() > (d+1)) )
		{
			// file
			index = s1.Mid(d+1,s1.Length()-d-1).asULONG();
			
			if ( index != oindex )
			{
				res.Empty();
				resnew.Empty();
				sharesize = 0;
				break;
			}
			
			oindex++;
			
			if ( si->GetFileBaseObject(index,&fbo) )
			{
				sharesize += fbo.m_nSize;

				CString sizestring = CString::number(fbo.m_nSize);
				res += s1.Left(d);
				res += '|';
				res += sizestring;
				res += fi;
				
				s1 = s1.Left(d).Replace("\x9","");
				
				resnew += "<File Name=\"";
				resnew += xml.ToUTF8(s1);
				resnew += "\" Size=\"";
				resnew += sizestring;
				resnew += "\" TTH=\"";
				resnew += si->GetHash(fbo.m_nHashIndex);
				resnew += "\"/>\n";
				
				/* Add file to tree */
				current->AddFile( index );
			}
		}
		else
		{
			// path
			res += s1;
			res += fi;
			
			// 0.3.14 folders added to search index
			oindex++;

			// dir end node
			for(den=0;den<s1.Length();den++)
				if ( s1.Data()[den] != '\x9' )
					break;

			while ( deo >= den )
			{
				dde--;
				deo--;
				resnew += "</Directory>\n";
				
				/* Change to parent item */
				current = (CShareTreeFolder*) current->GetParent();
			}
			dde++;
			resnew += "<Directory Name=\"";
			s1 = s1.Replace("\x9","");
			resnew += xml.ToUTF8(s1);
			resnew += "\">\n";

			deo = den;
			
			/* Add folder to tree and change to it */
			current = current->AddFolder( s1 );
		}
		
		j = i+2;
	}

	if ( resnew.NotEmpty() )
	{
		while(dde)
		{
			resnew += "</Directory>\n";
			dde--;
		}
		resnew += "</FileListing>\n";
	}

	//printf("%s\n",resnew.Data());
	
	// dclib now uses the operating system encoding if remote encoding is set properly
	// get local system encoding
	CIconv iconv( CConfig::Instance()->GetLocalEncoding(), CConfig::Instance()->GetRemoteEncoding() );
	res = iconv.encode( res );
	
	CreateBuffer( esbtHE3, res );
	CreateBuffer( esbtBZ, res );
	CreateBuffer( esbtXMLBZ, resnew );

	m_nShareSize = sharesize;

	m_MutexShareList.UnLock();
	
	/* no longer needed and it is quite large so free it */
	m_sIndexShareBuffer.Empty();
}

/** */
void CShareList::CreateBuffer( enum eShareBufferType e, const CString & sharebuffer )
{
	if ( e == esbtHE3 )
	{
		delete m_pHE3ShareBuffer;
		m_pHE3ShareBuffer = 0;
		
		if ( sharebuffer.NotEmpty() )
		{
			// he3 compression
			CHE3 * he3 = new CHE3();
			m_pHE3ShareBuffer = he3->encode_he3_data(&sharebuffer);
			delete he3;

			if ( m_pHE3ShareBuffer == 0 )
			{
				printf("[ERROR] he3 compression failed\n");
			}
		}
	}
	else if ( e == esbtBZ )
	{
		delete m_pBZShareBuffer;
		m_pBZShareBuffer = 0;

		if ( sharebuffer.NotEmpty() )
		{
			// bz compression
			m_pBZShareBuffer = new CByteArray();

			if ( !CBZ::Compress(&sharebuffer,m_pBZShareBuffer) )
			{
				delete m_pBZShareBuffer;
				m_pBZShareBuffer = 0;
				printf("[ERROR] bz2 compression failed\n");
			}
		}
	}
	else if ( e == esbtXMLBZ )
	{
		delete m_pXMLBZShareBuffer;
		m_pXMLBZShareBuffer = 0;

		if ( sharebuffer.NotEmpty() )
		{
			// bz compression
			m_pXMLBZShareBuffer = new CByteArray();

			if ( !CBZ::Compress(&sharebuffer,m_pXMLBZShareBuffer) )
			{
				delete m_pXMLBZShareBuffer;
				m_pXMLBZShareBuffer = 0;
				printf("[ERROR] xmlbz2 compression failed\n");
			}
		}
	}
}

/** */
int CShareList::GetShareBuffer( eShareBufferType type, CByteArray * sharebuffer, bool decompress )
{
	int err = 0;

	m_MutexShareList.Lock();

	if( !sharebuffer )
	{
		err = -1;
	}
	else
	{
		sharebuffer->SetSize(0);

		switch(type)
		{
			case esbtHE3:
				if ( !m_pHE3ShareBuffer )
					err = -1;
				else if ( decompress )
				{
					CHE3 * he3 = new CHE3();
					CString * s = he3->decode_he3_data(m_pHE3ShareBuffer);
					delete he3;
					if ( s )
					{
						sharebuffer->Append(s->Data(),s->Length());
						delete s;
					}
					else
						err = -1;
				}
				else
					sharebuffer->Append(m_pHE3ShareBuffer->Data(),m_pHE3ShareBuffer->Size());
				break;
			case esbtBZ:
				if ( !m_pBZShareBuffer )
					err = -1;
				else if ( decompress )
				{
					if ( !CBZ::Decompress( m_pBZShareBuffer, sharebuffer ) )
						err = -1;
				}
				else
					sharebuffer->Append(m_pBZShareBuffer->Data(),m_pBZShareBuffer->Size());
				break;
			case esbtXMLBZ:
				if ( !m_pXMLBZShareBuffer )
					err = -1;
				else if ( decompress )
				{
					if ( !CBZ::Decompress( m_pXMLBZShareBuffer, sharebuffer ) )
						err = -1;
				}
				else
					sharebuffer->Append(m_pXMLBZShareBuffer->Data(),m_pXMLBZShareBuffer->Size());
				break;
			case esbtXML:
				if ( !m_pXMLBZShareBuffer )
					err = -1;
				else
				{
					if ( !CBZ::Decompress( m_pXMLBZShareBuffer, sharebuffer ) )
						err = -1;
				}
				break;
			default:
				err = -1;
				break;
		}
	}

	m_MutexShareList.UnLock();

	return err;
}

/** */
unsigned long CShareList::GetShareBufferSize( eShareBufferType type )
{
	unsigned long i = 0;

	m_MutexShareList.Lock();

	switch(type)
	{
		case esbtHE3:
			if ( m_pHE3ShareBuffer )
				i = m_pHE3ShareBuffer->Size();
			break;
		case esbtBZ:
			if ( m_pBZShareBuffer )
				i = m_pBZShareBuffer->Size();
			break;
		case esbtXMLBZ:
			if ( m_pXMLBZShareBuffer )
				i = m_pXMLBZShareBuffer->Size();
			break;
		case esbtXML:
			if ( m_pXMLBZShareBuffer )
			{
				CByteArray * ba = new CByteArray();
				if ( GetShareBuffer( esbtXML, ba ) > 0 )
				{
					i = ba->Size();
				}
				delete ba;
			}
			break;
		default:
			break;
	}

	m_MutexShareList.UnLock();

	return i;
}

/** */
ulonglong CShareList::GetShareSize() const
{
	return m_nShareSize;
}

/** */
void CShareList::GetPartialListing( const CString & dir, CString & result, int depth, CSearchIndex * si )
{
	if ( (dir == "/") && (depth == -1) )
	{
		CByteArray * ba = new CByteArray();
		if ( GetShareBuffer( esbtXML, ba ) > 0 )
		{
			result = (const char*) ba->Data();
		}
		delete ba;
	}
	else
	{
		m_MutexShareList.Lock();
		
		/* All we need to do is find the correct folder item and call GetXML() */
		if ( m_pRootItem )
		{
			CShareTreeFolder * current = m_pRootItem;
			int start = 0;
			int end = 0;
			CString key;
			
			for ( ; ; )
			{
				start = dir.Find('/',start) + 1;
				end = dir.Find('/',start);
				if ( end == -1 )
				{
					break;
				}
				else
				{
					std::list<CShareTreeFolder*> * list = current->GetChildren();
					
					if ( list )
					{
						key = dir.Mid( start, end - start );
						
						for ( std::list<CShareTreeFolder*>::const_iterator it = list->begin(); it != list->end(); ++it )
						{
							if ( (*it)->GetName() == key )
							{
								current = (*it);
								delete list;
								list = 0;
								break;
							}
						}
						
						if ( list ) // folder not found!
						{
							delete list;
							current = 0;
							break;
						}
					}
					else
					{
						current = 0;
						break;
					}
				}
			}
			
			if ( current )
			{
				/* XML header and opening FileListing tag */
				result  = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n";
				result += "<FileListing Version=\"1\" Generator=\"dclib ";
				result += DCLIB_VERSION_STRING;
				/* FIXME CID="base32_encoded_data" is required! */
				result += "\" Base=\"";
				result += CXml().ToUTF8(dir);
				result += "\">\n";

				result += current->GetXML( depth, si );
				
				/* Close FileListing tag */
				result += "</FileListing>\n";
			}
		}
		
		m_MutexShareList.UnLock();
	}
	
	//printf("GetPartialListing='%s'\n",result.Data());
}
