/***************************************************************************
                          chublistmanager.cpp  -  description
                             -------------------
    begin                : Don Mai 16 2002
    copyright            : (C) 2002-2003 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 "chublistmanager.h"

#include "cconfig.h"
#include "core/cmanager.h"
#include "dcobject.h"
#include "core/cbytearray.h"
#include "chttp.h"
#include "core/cbz.h"
#include "core/cxml.h"
#include "core/ccallback.h"
#include "core/ciconv.h"

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

#include <stdio.h>

/** */
CHubListManager::CHubListManager()
{
	m_pCallback = new CCallback0<CHubListManager>( this, &CHubListManager::Callback );
	CManager::Instance()->Add( m_pCallback );

	if ( CConfig::Instance()->GetReloadHubListTime() != 0 )
	{
		m_nReloadHubListTimeout = time(0) + CConfig::Instance()->GetReloadHubListTime()*60*60;
	}
	else
	{
		m_nReloadHubListTimeout = 0;
	}

	// get hublist stuff
	m_pHttp           = 0;
	m_pHubListUrlList = 0;
	m_pHubListUrl     = 0;
	m_pHubListData    = 0;

	m_bGetHubListDone = false;
	
	m_pXml = 0;
	m_pXmlHubs = new CList<DCConfigHubItem>;
}

/** */
CHubListManager::~CHubListManager()
{
	m_Thread.Stop();

	SetInstance(0);

	CManager::Instance()->Remove( m_pCallback );

	delete m_pCallback;
	m_pCallback = 0;
	
	delete m_pXml;
	m_pXml = 0;
	
	delete m_pXmlHubs;
	m_pXmlHubs = 0;
}

/** thread callbackfunction */
int CHubListManager::Callback()
{
	m_Thread.Lock();

	// check reload hublist timeout
	if ( CConfig::Instance() )
	{
		if ( CConfig::Instance()->GetReloadHubListTime() != 0 )
		{
			if ( m_nReloadHubListTimeout != 0 )
			{
				if ( time(0) >= m_nReloadHubListTimeout )
				{
					GetPublicHubList();

					m_nReloadHubListTimeout = time(0) + CConfig::Instance()->GetReloadHubListTime()*60*60;
				}
			}
			else // change from config
			{
				m_nReloadHubListTimeout = time(0) + CConfig::Instance()->GetReloadHubListTime()*60*60;
			}
		}
		else
		{
			m_nReloadHubListTimeout = 0;
		}
	}

	// cleanup all objects
	if ( m_bGetHubListDone )
	{
		delete m_pHttp;
		m_pHttp = 0;

		delete m_pHubListUrlList;
		m_pHubListUrlList = 0;

		delete m_pHubListData;
		m_pHubListData = 0;

		if ( m_pXmlHubs )
		{
			m_pXmlHubs->Clear();
		}

		m_pHubListUrl = 0;

		DCMessageGetHubList * msg = new DCMessageGetHubList();

		msg->m_bRun = false;

		if ( DC_CallBack(msg) == -1 )
		{
			delete msg;
		}

		m_bGetHubListDone = false;
	}

	m_Thread.UnLock();

	return 0;
}

/** http callback function */
int CHubListManager::HttpCallBack( CDCMessage * dcmsg )
{
	CByteArray in;

	switch ( dcmsg->m_eType )
	{
		case DC_MESSAGE_CONNECTION_STATE:
		{
			CMessageConnectionState *msg = (CMessageConnectionState*)dcmsg;

			if ( msg->m_eState == estDISCONNECTED )
			{
				if ( (m_pHttp->GetHttpError() == 200) && m_pHttp->GetData(&in) )
				{
					HandleHubListData( m_pHttp->GetUrl(), &in );
				}

				// redirect
				if ( m_pHttp->GetHttpError() == 302 )
				{
					m_pHttp->GetUrl(m_pHttp->GetLocation());
				}
				else if ( NextHubListUrl() == false )
				{
					// parse public hub list in own thread
					m_Thread.SetThreadCallBackFunction( new CCallback0<CHubListManager>( this, &CHubListManager::ParsePublicHubList ) );
					m_Thread.Start();
				}
			}

			break;
		}

		case DC_MESSAGE_TRANSFER:
		{
			if ( DC_CallBack( (CMessageTransfer*)dcmsg ) != -1 )
			{
				dcmsg = 0;
			}

			break;
		}

		default:
		{
			break;
		}
	}

	if ( dcmsg )
	{
		delete dcmsg;
	}

	return 0;
}

/** */
void CHubListManager::HandleHubListData( const CString & url, CByteArray * in )
{
	CByteArray out;
	
	if ( url.Right(4).ToLower() == ".bz2" )
	{
		if ( CBZ::Decompress( in, &out ) )
		{
			if ( url.Right(8).ToLower() == ".xml.bz2" )
			{
				if ( m_pXml == 0 )
				{
					m_pXml = new CXml();
				}
				
				if ( m_pXml->ParseFixMemory(&out) && m_pXml->DocFirstChild() )
				{
					ParseXmlPublicHubList();
				}
				else
				{
					printf("Failed to parse XML hublist.\n");
				}
				
				delete m_pXml;
				m_pXml = 0;
			}
			else
			{
				m_pHubListData->Append(out.Data(),out.Size());
				m_pHubListData->Append("\xD\xA",2);
			}
		}
		else
		{
			printf("bz2 decompress failed\n");
		}
	}
	else
	{
		if ( url.Right(4).ToLower() == ".xml" )
		{
			if ( m_pXml == 0 )
			{
				m_pXml = new CXml();
			}
			
			if ( m_pXml->ParseFixMemory(in) && m_pXml->DocFirstChild() )
			{
				ParseXmlPublicHubList();
			}
			else
			{
				printf("Failed to parse XML hublist.\n");
			}
			
			delete m_pXml;
			m_pXml = 0;
		}
		else
		{
			m_pHubListData->Append(in->Data(),in->Size());
			m_pHubListData->Append("\xD\xA",2);
		}
	}
}

/** */
bool CHubListManager::GetPublicHubList()
{
	bool res = false;

	if ( m_pHttp != 0 )
	{
		// get hublist allready running
		return res;
	}

	m_pHubListUrlList = new CList<DCConfigHubListUrl>();

	// get all hublist urls
	CConfig::Instance()->GetHubListUrlList(m_pHubListUrlList);

	// check hublist url count
	if ( m_pHubListUrlList->Count() == 0 )
	{
		delete m_pHubListUrlList;
		m_pHubListUrlList = 0;
		return res;
	}

	// init first url
	m_pHubListUrl = 0;

	m_pHubListData = new CByteArray();
	m_pHttp = new CHttp();
	m_pHttp->SetCallBackFunction( new CCallback1<CHubListManager, CDCMessage*>( this, &CHubListManager::HttpCallBack ) );

	res = NextHubListUrl();

	if ( !res )
	{
		m_bGetHubListDone = true;
	}
	else
	{
		DCMessageGetHubList * msg = new DCMessageGetHubList();

		msg->m_bRun = true;

		if ( DC_CallBack(msg) == -1 )
		{
			delete msg;
		}
	}

	return res;
}

/** */
int CHubListManager::ParsePublicHubList()
{
	CString line;
	CString s;
	CString s1,s2,s3,s4;
	long i=0,i1=0;

	DCConfigHubItem * hubitem = 0;
	
	if ( (m_pHubListData->Size() == 0) && (m_pXmlHubs->Count() == 0) )
	{
		m_Thread.Stop(false);
		m_Thread.SetThreadCallBackFunction(0);
		m_bGetHubListDone = true;

		return 0;
	}
	
	while ( (hubitem = m_pXmlHubs->Next(hubitem)) != 0 )
	{
		// name, host, description, usercount, country, shared, minshare, extra
		CConfig::Instance()->AddPublicHub(
			hubitem->m_sName,
			hubitem->m_sHost,
			hubitem->m_sDescription,
			hubitem->m_nUserCount,
			hubitem->m_sCountry,
			hubitem->m_nShared,
			hubitem->m_nMinShare,
			hubitem->m_sExtra
		);
	}
	
	if ( m_pHubListData->Size() > 0 )
	{
	
	s.Set((const char*)m_pHubListData->Data(),m_pHubListData->Size());

	CIconv * pIconv = new CIconv( CConfig::Instance()->GetRemoteEncoding(), CConfig::Instance()->GetLocalEncoding() );

	while( (i = s.Find(0x0d,i)) != -1 )
	{
		line = s.Mid(i1,i-i1);

		if ( !line.IsEmpty() )
		{
			s1 = line.Section( '|', 0, 0 );
			s2 = line.Section( '|', 1, 1 );
			s3 = line.Section( '|', 2, 2 );
			s4 = line.Section( '|', 3, 3 );

			// replace all spaces
			s2 = s2.Replace(" ","");

			// remove line ends
			s1 = s1.Replace("\n", "");

			// name, host, description, usercount
			CConfig::Instance()->AddPublicHub( pIconv->encode(s1), pIconv->encode(s2), pIconv->encode(s3), s4.asULL() );
		}

		i1 = i+2;
		i += 2;
	}

	delete pIconv;
	
	}

	// store the list
	if ( CConfig::Instance()->GetHubListStoreLocal() )
	{
		CConfig::Instance()->SaveDCHub();
	}

	m_Thread.Stop(false);
	m_Thread.SetThreadCallBackFunction(0);
	m_bGetHubListDone = true;

	return 0;
}

/** */
bool CHubListManager::NextHubListUrl()
{
	bool res = false;

	while( (m_pHubListUrl=m_pHubListUrlList->Next(m_pHubListUrl)) != 0 )
	{
		if ( m_pHubListUrl->bEnabled )
		{
			if ( m_pHubListUrl->sUrl.Left(7) == "file://" )
			{
				CByteArray * data = new CByteArray();
				if ( data->LoadFromFile(m_pHubListUrl->sUrl.Mid(7)) )
				{
					HandleHubListData( m_pHubListUrl->sUrl, data );
				}
				delete data;
				
				if ( NextHubListUrl() == false )
				{
					// parse public hub list in own thread
					m_Thread.SetThreadCallBackFunction( new CCallback0<CHubListManager>( this, &CHubListManager::ParsePublicHubList ) );
					m_Thread.Start();
				}
				
				res = true;
				break;
			}
			else if ( m_pHubListUrl->sUrl.NotEmpty() )
			{
				m_pHttp->GetUrl(m_pHubListUrl->sUrl);

				res = true;
				break;
			}
		}
	}

	return res;
}

/** */
int CHubListManager::ParseXmlPublicHubList()
{
	int count = 0;
	
	printf("Parse XML hub list...\n");
	
	do
	{
		if ( (m_pXml->Name() == "Hublist") && m_pXml->FirstChild() )
		{
			CList<CXmlColumn> * cols = FindAndParseXmlColumns();
			
			if ( !cols )
			{
				/*
				 * The column headings are only needed so that the Extra
				 * field is filled in.
				 */
				printf("ParseXmlPublicHubList: no column headings found, trying with defaults\n");
				
				cols = new CList<CXmlColumn>;
				
				CXmlColumn * xmlcol = new CXmlColumn();
				xmlcol->m_sName = "Name";
				xmlcol->m_sType = "string";
				cols->Add(xmlcol);
				
				xmlcol = new CXmlColumn();
				xmlcol->m_sName = "Address";
				xmlcol->m_sType = "string";
				cols->Add(xmlcol);
				
				xmlcol = new CXmlColumn();
				xmlcol->m_sName = "Description";
				xmlcol->m_sType = "string";
				cols->Add(xmlcol);

				xmlcol = new CXmlColumn();
				xmlcol->m_sName = "Port";
				xmlcol->m_sType = "int";
				cols->Add(xmlcol);
				
				xmlcol = new CXmlColumn();
				xmlcol->m_sName = "Users";
				xmlcol->m_sType = "int";
				cols->Add(xmlcol);
			}
			
			/* go back to start */
			m_pXml->DocFirstChild();
			m_pXml->FirstChild();
			
			do
			{
				if ( (m_pXml->Name() == "Hubs") && m_pXml->FirstChild() )
				{
					count += ParseXmlHubs( cols );
					m_pXml->Parent();
				}
			}
			while ( m_pXml->NextNode() );
			m_pXml->Parent();
			
			cols->Clear();
			delete cols;
		}
	}
	while ( m_pXml->NextNode() );
	
	printf("XML hublist: %d hubs\n", count);
	
	return count;
}

/** */
int CHubListManager::ParseXmlHubs( CList<CXmlColumn> * cols )
{
	int count = 0;
	
	do
	{
		if ( (m_pXml->Name() == "Hub") )
		{
			ParseXmlHub(cols);
			count++;
		}
	}
	while ( m_pXml->NextNode() );
	
	return count;
}

/** */
CList<CXmlColumn> * CHubListManager::FindAndParseXmlColumns()
{
	CList<CXmlColumn> * cols = 0;
	
	do
	{
		if ( (m_pXml->Name() == "Columns") && m_pXml->FirstChild() )
		{
			cols = new CList<CXmlColumn>;
			do
			{
				if ( m_pXml->Name() == "Column" )
				{
					CXmlColumn * col = new CXmlColumn();
					col->m_sName = m_pXml->Prop("Name");
					col->m_sType = m_pXml->Prop("Type");
					cols->Add(col);
				}
			}
			while ( m_pXml->NextNode() );
			break;
		}
		
		if ( m_pXml->FirstChild() )
		{
			do
			{
				if ( (m_pXml->Name() == "Columns") && m_pXml->FirstChild() )
				{
					cols = new CList<CXmlColumn>;
					do
					{
						if ( m_pXml->Name() == "Column" )
						{
							CXmlColumn * col = new CXmlColumn();
							col->m_sName = m_pXml->Prop("Name");
							col->m_sType = m_pXml->Prop("Type");
							cols->Add(col);
						}
					}
					while ( m_pXml->NextNode() );
					break;
				}
			}
			while ( m_pXml->NextNode() );
			
			if ( cols )
			{
				break;
			}
		}
	}
	while ( m_pXml->NextNode() );
	
	/* no CXml::Parent() called since ParseXmlPublicHubList() just goes back to the start */
	
	return cols;
}

/** */
void CHubListManager::ParseXmlHub( CList<CXmlColumn> * cols )
{
	CXmlColumn * col = 0;
	DCConfigHubItem * hubitem = new DCConfigHubItem();
	CString port,namelc;
	
	while ( (col = cols->Next(col)) != 0 )
	{
		col->m_sValue = m_pXml->Prop(col->m_sName);
		
		namelc = col->m_sName.ToLower();
		if (namelc == "name")
		{
			hubitem->m_sName = col->m_sValue;
		}
		else if (namelc == "address")
		{
			hubitem->m_sHost = col->m_sValue;
		}
		else if (namelc == "description")
		{
			hubitem->m_sDescription = col->m_sValue;
		}
		else if (namelc == "users")
		{
			hubitem->m_nUserCount = col->m_sValue.asULL();
		}
		else if (namelc == "port")
		{
			port = col->m_sValue;
		}
		else if (namelc == "country")
		{
			hubitem->m_sCountry = col->m_sValue;
		}
		else if (namelc == "minshare")
		{
			hubitem->m_nMinShare = col->m_sValue.asULL();
		}
		else if (namelc == "shared")
		{
			hubitem->m_nShared = col->m_sValue.asULL();
		}
		else
		{
			hubitem->m_sExtra += col->m_sName;
			hubitem->m_sExtra += '=';
			hubitem->m_sExtra += col->m_sValue;
			hubitem->m_sExtra += ' ';
		}
		
		//printf("Name=%s Type=%s Value=%s\n", col->m_sName.Data(), col->m_sType.Data(), col->m_sValue.Data());
	}
	
	if ( (hubitem->m_sHost.Find(':') < 0) && (port.NotEmpty()) )
	{
		hubitem->m_sHost += ':';
		hubitem->m_sHost += port;
	}
	
	if ( hubitem->m_sName.IsEmpty() || hubitem->m_sHost.IsEmpty() )
	{
		delete hubitem;
	}
	else
	{
		m_pXmlHubs->Add(hubitem);
	}
}
