/***************************************************************************
                          dcupdatemanager.cpp  -  description
                             -------------------
    begin                : Sun May 2 2004
    copyright            : (C) 2004 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 <stdlib.h>

#include <qapplication.h>
#include <qobject.h>
#include <qfile.h>
#include <qpushbutton.h>
#include <qtextedit.h>
#include <qdatetime.h>

#include <dclib/dcos.h>
#include <dclib/chttp.h>
#include <dclib/core/ccallback.h>
#include <dclib/core/cbz.h>
#include <dclib/dcobject.h>
#include <dclib/core/cxml.h>
#include <dclib/core/cbytearray.h>

#include <dcconfig.h>
#include <dciconloader.h>

#include "dcupdatemanager.h"

#define UPDATE_URL	"http://-server-/update.xml"

DCUpdateManager * g_pUpdateManager = 0;

/** */
DCUpdateManager::DCUpdateManager(QWidget* parent, const char *name, int wflags)
	: DCDialogUpdateManager(parent,name,wflags)
{
	// set default icon
	setIcon( g_pIconLoader->GetPixmap(eiINFO) );
	
	m_bRun    = FALSE;
	m_Timeout = 0;
	m_pHttp   = 0;
	m_bUpdate = FALSE;

	m_pUpdateListNew = 0;
	m_pUpdateListOld = 0;
	
	m_pMessageList = new CList<CObject>();
	
	InitDocument();
	
	g_pUpdateManager = this;
}

/** */
DCUpdateManager::~DCUpdateManager()
{
	m_Timer.stop();

	g_pUpdateManager = NULL;
	
	m_UMThread.Lock();

	if ( m_pMessageList )
	{
		delete m_pMessageList;
		m_pMessageList = 0;
	}

	m_UMThread.UnLock();
}

/** */
void DCUpdateManager::InitDocument()
{
	PushButton_UPDATE->setEnabled(FALSE);

	connect( PushButton_UPDATE, SIGNAL(clicked()), this, SLOT(slotUpdate()) );
	connect( PushButton_CHECKUPDATE, SIGNAL(clicked()), this, SLOT(slotCheckUpdate()) );
	
	connect( &m_Timer, SIGNAL(timeout()), this, SLOT(timerDone()) );
	m_Timer.start( 1000, TRUE );
}

/** */
void DCUpdateManager::AppendLog( QString s )
{
	TextEdit_UPDATE->append( QTime::currentTime().toString("[hh:mm:ss]") + " " + s + "\n" );
}

/** callback function */
int DCUpdateManager::DC_CallBack( CObject * Object )
{
	int err;

	m_UMThread.Lock();

	if ( Object != NULL )
	{
		if ( m_pMessageList != NULL )
		{
			m_pMessageList->Add(Object);
			err = 0;
		}
		else
		{
			err = -1;
		}
	}
	else
	{
		err = -1;
	}

	m_UMThread.UnLock();

	return err;
}

/** */
void DCUpdateManager::slotCheckUpdate()
{
	if ( (m_pUpdateListNew != 0) && (m_pUpdateListOld != 0) )
	{
		return;
	}

	PushButton_CHECKUPDATE->setEnabled(FALSE);
	m_Timeout = 0;
}

/** */
void DCUpdateManager::slotUpdate()
{
	PushButton_UPDATE->setEnabled(FALSE);

	m_bUpdate = TRUE;
}

/** */
void DCUpdateManager::timerDone()
{
	CByteArray in;
	CObject * Object;
	CDCMessage *DCMsg;
	int i;

	if ( m_bRun == FALSE )
	{
		if ( m_pHttp )
		{
			if ( m_sUrl == UPDATE_URL )
			{
				// get content
				if ( (m_pHttp->GetHttpError() == 200) && (m_pHttp->GetData(&in) == TRUE) )
				{
					CheckConfig(&in);
					
					if ( (m_pUpdateListNew != 0) && (m_pUpdateListOld != 0) )
					{
						PushButton_UPDATE->setEnabled(TRUE);
					}
				}
				
				PushButton_CHECKUPDATE->setEnabled(TRUE);
			}
			else
			{
				DCUpdateEntry * uo=0,* un = 0;
				
				while((un=m_pUpdateListNew->Next(un))!=0)
				{
					if ( un->m_sUrl == m_sUrl )
					{
						break;
					}
				}
				
				if ( un )
				{
					// get content
					if ( (m_pHttp->GetHttpError() == 200) && (m_pHttp->GetData(&in) == TRUE) )
					{
						// save centent
						if ( SaveContent( un, &in ) == TRUE )
						{
							uo = 0;
							while((uo=m_pUpdateListOld->Next(uo))!=0)
							{
								if ( un->m_sName == uo->m_sName )
								{
									m_pUpdateListOld->Del(uo);
									m_pUpdateListNew->Remove(un);
									m_pUpdateListOld->Add(un);
									break;
								}
							}
						
							if ( uo == 0 )
							{
								// new entry
								m_pUpdateListNew->Remove(un);
								m_pUpdateListOld->Add(un);
							}
							
							SaveConfig(m_pUpdateListOld);
						}

					}
					else
					{
						// remove old entry
						m_pUpdateListNew->Del(un);
						
						AppendLog( tr("Download failed") );
					}
				}

				// fnished
				if ( m_pUpdateListNew->Count() == 0 )
				{
					delete m_pUpdateListNew;
					m_pUpdateListNew = 0;
					
					// save update config
					SaveConfig(m_pUpdateListOld);
					
					delete m_pUpdateListOld;
					m_pUpdateListOld = 0;
					
					m_bUpdate = FALSE;
					
					AppendLog( tr("Update finished") );
				}
			}
			
			delete m_pHttp;
			m_sUrl    = "";
			m_pHttp   = 0;
			m_Timeout = time(0);
		}
		
		if ( (m_pUpdateListNew != 0) && (m_pUpdateListOld != 0) )
		{
			if ( m_bUpdate == TRUE )
			{
				if ( m_pUpdateListNew->Next(0) )
				{
					m_bRun = GetUrl( m_pUpdateListNew->Next(0)->m_sUrl );
				}

				if ( m_bRun )
				{
					AppendLog( tr("Download content ") + m_pUpdateListNew->Next(0)->m_sName.Data() );
				}
				else
				{
					AppendLog( tr("Download content failed") );
				}
			}
		}
		else if ( ((m_Timeout == 0) || ((time(0)-m_Timeout) > 60*60)) )
		{
			m_bRun = GetUrl(UPDATE_URL);
			
			if ( m_bRun )
			{
				PushButton_CHECKUPDATE->setEnabled(FALSE);
				AppendLog( tr("Download update.xml") );
			}
			else
			{
				PushButton_CHECKUPDATE->setEnabled(TRUE);
				AppendLog( tr("Download update.xml failed") );
			}
		}
	}
	
	for(i=0;i<50;i++)
	{
		if ( m_UMThread.TryLock() == FALSE )
		{
			break;
		}

		if ( m_pMessageList != 0 )
		{
			Object = m_pMessageList->Next(0);

			if ( Object != 0 )
			{
				m_pMessageList->Remove(Object);
			}
		}
		else
		{
			Object = 0;
		}

		m_UMThread.UnLock();

		if ( Object == 0 )
		{
			break;
		}

		DCMsg = (CDCMessage*) Object;

		switch ( DCMsg->m_eType )
		{
			case DC_MESSAGE_TRANSFER:
			{
				// CMessageTransfer *msg = (CMessageTransfer*)Object;

				break;
			}

			default:
			{
				break;
			}
		}

		if ( Object )
		{
			delete Object;
		}
	}

	m_Timer.start( 1000, TRUE );
}

/** http callback function */
int DCUpdateManager::HttpCallBack( CObject *, CObject * object )
{
	switch(((CDCMessage*)object)->m_eType)
	{
		case DC_MESSAGE_CONNECTION_STATE:
		{
			CMessageConnectionState *msg = (CMessageConnectionState*)object;

			if ( msg->m_eState == estDISCONNECTED )
			{
				if ( (m_pHttp->GetHttpError() == 200) && (m_pHttp->GetData(0) == TRUE) )
				{
					printf("finished\n");
					m_bRun = FALSE;
				}
				
				// redirect
				if ( m_pHttp->GetHttpError() == 302 )
				{
					m_pHttp->GetUrl(m_pHttp->GetLocation());
				}
				else
				{
					m_bRun = FALSE;
				}
			}

			break;
		}

		case DC_MESSAGE_TRANSFER:
		{
			if ( DC_CallBack(object) != -1 )
			{
				object = 0;
			}

			break;
		}

		default:
		{
			break;
		}
	}

	if ( object )
	{
		delete object;
	}

	return 0;
}

/** */
bool DCUpdateManager::GetUrl( CString url )
{
	bool res = FALSE;

	if ( m_pHttp )
	{
		delete m_pHttp;
	}
	
	m_pHttp = new CHttp();
	m_pHttp->SetCallBackFunction( new CCallback<DCUpdateManager>( this, &DCUpdateManager::HttpCallBack ) );
	m_sUrl = url;
				
	if ( m_pHttp->GetUrl(m_sUrl) == 0 )
	{
		res = TRUE;
	}

	if ( !res )
	{
		// error
		delete m_pHttp;
		m_pHttp = 0;
		m_sUrl = "";
	}
	
	return res;
}

/** */
CList<DCUpdateEntry> * DCUpdateManager::ParseConfig( CByteArray * in )
{
	CXml * xml;
	xmlNodePtr n1,n2;
	CList<DCUpdateEntry> * list;

	// sanity check
	if ( !in )
	{
		return 0;
	}
	
	list = new CList<DCUpdateEntry>();
	
	xml = new CXml();
	
	if ( xml->ParseMemory( (const char*)in->Data(), in->Size() ) == TRUE )
	{
		for(n1=xml->doc()->children;n1!=0;n1=n1->next)
		{
			if ( xml->name(n1) == "update" )
			{
				for(n2=n1->xmlChildrenNode;n2!=0;n2=n2->next)
				{
					if ( xml->name(n2) == "entry" )
					{
						DCUpdateEntry * entry;
						
						entry = new DCUpdateEntry();
						
						//printf("entry found %s\n",xml->prop(n2,"name").Data());

						entry->m_sName    = xml->prop(n2,"name");
						entry->m_sVersion = xml->prop(n2,"version");
						entry->m_nTime    = xml->prop(n2,"date").asULL();
						entry->m_sUrl     = xml->prop(n2,"url");
						entry->m_sComp    = xml->prop(n2,"comp");
						
						list->Add(entry);
					}
				}
			}
		}
	}
	
	delete xml;

	// remove a empty list
	if ( list->Count() == 0 )
	{
		delete list;
		list = 0;
	}
	
	return list;
}

/** */
CList<DCUpdateEntry> * DCUpdateManager::LoadConfig()
{
	CList<DCUpdateEntry> * list = 0;
	
	// load old list
	QFile f((g_pConfig->GetConfigPath()+DIRSEPARATOR+"update.xml").Data());
						
	if ( f.open(IO_ReadOnly) )
	{
		if ( f.size() > 0 )
		{
			CByteArray ba;
			ba.SetSize(f.size());
								
			if ( f.readBlock( (char*)ba.Data(), f.size()) == f.size() )
			{
				list = ParseConfig(&ba);
			}
		}
		
		f.close();
	}

	return list;
}

/** */
void DCUpdateManager::SaveConfig( CList<DCUpdateEntry> * list )
{
	CXml * xml;
	xmlDocPtr doc;
	xmlNodePtr node;
	CString s;
	DCUpdateEntry * ue = 0;
	
	xml = new CXml();

	doc = xmlNewDoc((const xmlChar*)"1.0");

	doc->children = xmlNewDocNode(doc,0,(const xmlChar*)"update",0);

	while( (ue=list->Next(ue)) != 0 )
	{
		node = xmlNewChild( doc->children, 0, (const xmlChar*)"entry", 0 );
		xml->xmlNewStringProp(node,"name",ue->m_sName);
		xml->xmlNewStringProp(node,"version",ue->m_sVersion);
		xml->xmlNewStringProp(node,"date",CString().setNum(ue->m_nTime));
		xml->xmlNewStringProp(node,"url",ue->m_sUrl);
		xml->xmlNewStringProp(node,"comp",ue->m_sComp);
	}

	s = g_pConfig->GetConfigPath()+DIRSEPARATOR+"update.xml";

	if ( xmlSaveFormatFile(s.Data(),doc,1) == -1 )
	{
	}

	xmlFreeDoc(doc);

	delete xml;
}

/** */
void DCUpdateManager::CheckConfig( CByteArray * in )
{
	DCUpdateEntry * uo=0,* un = 0;

	// parse content
	CList<DCUpdateEntry> * listnew = ParseConfig(in);
	CList<DCUpdateEntry> * listold = 0;
					
	if ( !listnew )
	{
		printf("error parse content\n");
		
		return;
	}
	
	listold = LoadConfig();
						
	if ( listold )
	{
		// compare date and remove old entry
		while((un=listnew->Next(un))!=0)
		{
			uo = 0;
			while((uo=listold->Next(uo))!=0)
			{
				if ( (un->m_sName == uo->m_sName) &&
				     (un->m_nTime <= uo->m_nTime) )
				{
					listnew->Del(un);
					un=0;
					break;
				}
				
				// check compression and remove bz2 if it not supported
#ifndef HAVE_LIBBZ2
				if ( un->m_sComp == "bz2" )
				{
					AppendLog( tr("Update available but no bz2 support for ") + un->m_sName.Data() );
					listnew->Del(un);
					un=0;
					break;
				}
#endif
			}
		}
	}
	else
	{
		listold = new CList<DCUpdateEntry>();
	}

	// update view
	un = 0;
	while((un=listnew->Next(un))!=0)
	{
		AppendLog( tr("Update available for ") + un->m_sName.Data() );
	}
	
	AppendLog( CString().setNum(listnew->Count()).Data() + tr(" update(s) available") );
						
	if ( listnew->Count() == 0 )
	{
		// no new updates
		delete listnew;
		delete listold;
		listnew = 0;
		listold = 0;
	}
						
	m_pUpdateListNew = listnew;
	m_pUpdateListOld = listold;
}

/** */
bool DCUpdateManager::SaveContent( DCUpdateEntry * entry, CByteArray * in )
{
	QString s;
	bool res = FALSE;
	CByteArray ba;
	
	s = (g_pConfig->GetConfigPath()+DIRSEPARATOR).Data();
	
	if ( entry->m_sName == "translator" )
	{
		s += "dctranslator.cfg";
	}
	else if ( entry->m_sName == "emoticons xml" )
	{
		s += "emoticons.xml";
	}
	else if ( entry->m_sName == "emoticons xpm" )
	{
		s += "emoticons.xpm";
	}
	else
	{
		printf("unknown content\n");
		return res;
	}
	
	ba.SetSize(0);
	
	// decompress data
	if ( entry->m_sComp == "bz2" )
	{
		CBZ bz;

		if ( bz.Decompress( in, &ba ) == FALSE )
		{
			ba.SetSize(0);
		}
	}
	else
	{
		ba.Append(in->Data(),in->Size());
	}
	
	if ( ba.Size() == 0 )
	{
		return res;
	}
	
	QFile f(s);
	
	if ( f.open(IO_WriteOnly|IO_Truncate) )
	{
		if ( f.writeBlock( (char*)ba.Data(), ba.Size()) == ba.Size() )
		{
			res = TRUE;
		}
	
		f.close();
	}
	
	// load new files
	if ( res == TRUE )
	{
		if ( entry->m_sName == "translator" )
		{
		}
		else if ( entry->m_sName == "emoticons xml" )
		{
			g_pConfig->InitMimeSourceFactory();
		}
		else if ( entry->m_sName == "emoticons xpm" )
		{
			g_pConfig->InitMimeSourceFactory();
		}
	}
	
	return res;
}
