/*
 * port.cc
 *
 * Copyright (c) 2002, 2003 Frerich Raabe <raabe@kde.org>
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. For licensing and distribution details, check the
 * accompanying file 'COPYING'.
 */
#include "databasewrapper.h"
#include "filelistretriever.h"
#include "port.h"
#include "portmgr.h"

#include <kdebug.h>
#include <klocale.h>
#include <knotifyclient.h>

#include <qdir.h>

#include <stdlib.h> // for getenv()

namespace Barry
{
	class ConcretePort : public PortInterface
	{
		public:
			ConcretePort( const QString &origin );

			virtual QString origin() const;
			virtual QString name() const;
			virtual QString version() const;
			virtual QString installedVersion() const;
			virtual QString directory() const;
			virtual QString prefix() const;
			virtual QString comment() const;
			virtual QString description() const;
			virtual QString maintainer() const;
			virtual QStringList categories() const;
			virtual PortList buildDeps() const;
			virtual PortList runDeps() const;
			virtual QString link() const;
			virtual QStringList files() const;
			virtual QStringList installedFiles() const;
			virtual bool installed() const;
			virtual long long size() const;

		private:
			void determineSize();
			PortList parseDeps( const QString &depString ) const;

			QString m_origin;
			QString m_name;
			QString m_version;
			QString m_directory;
			QString m_prefix;
			QString m_comment;
			QString m_description;
			QString m_maintainer;
			QStringList m_categories;
			PortList m_buildDeps;
			PortList m_runDeps;
			QString m_link;
			long long m_size;
	};
};

// This would be nice in Qt
template <class Container>
Container qUnique( const Container &container )
{
	Container c;
	typename Container::ConstIterator it = container.begin();
	typename Container::ConstIterator end = container.end();
	for ( ; it != end; ++it )
		if ( c.find( *it ) == c.end() )
			c << *it;
	return c;
}

using namespace Barry;

PortInterface::~PortInterface()
{
}

struct Port::Private
{
	Private(): count( 1 ), initialized( false ), port( 0 ) { }
	~Private() { delete port; }

	void ref() { ++count; }
	bool deref() { return !--count; }
	unsigned int count;

	bool initialized;
	QString origin;
	QString name;
	QString version;
	ConcretePort *port;
};

void Port::parsePkgName( const QString &pkgName,
                         QString &name, QString &version )
{
	const int lastDash = pkgName.findRev( '-' );

	name = pkgName.left( lastDash );
	version = pkgName.mid( lastDash + 1 );
}

Port Port::fromPkgName( const QString &origin, const QString &pkgName )
{
	QString name;
	QString version;

	parsePkgName( pkgName, name, version );

	return Port( origin, name, version );
}

Port::Port() : PortInterface(), d( 0 )
{
}

Port::Port( const Port &rhs ) : PortInterface(), d( new Private )
{
	*this = rhs;
}

Port::Port( const QString &origin ) : PortInterface(), d( new Private )
{
	d->origin = origin;
}

Port::Port( const QString &origin,
            const QString &name, const QString &version)
	: PortInterface(), d( new Private )
{
	d->origin = origin;
	d->name = name;
	d->version = version;
}

bool Port::operator==( const Port &rhs ) const
{
	if ( !d || !rhs.d )
		return d == rhs.d;
	return d->origin == rhs.d->origin;
}

bool Port::operator<( const Port &rhs ) const
{
	if ( !d || !rhs.d )
		return false;
	return d->origin < rhs.d->origin;
}

Port &Port::operator=( const Port &rhs )
{
	if ( this != &rhs ) {
		if ( rhs.d )
			rhs.d->ref();
		if ( d && d->deref() )
			delete d;
		d = rhs.d;
	}
	return *this;
}

Port::~Port()
{
	if ( d && d->deref() )
		delete d;
}

QString Port::origin() const
{
	Q_ASSERT( d );
	return d->origin;
}

QString Port::name() const
{
	Q_ASSERT( d );
	if ( d->port )
		return d->port->name();

	if ( !d->initialized )
		determineAttributes();

	return d->name;
}

QString Port::version() const
{
	Q_ASSERT( d );
	if ( d->port )
		return d->port->version();

	if ( !d->initialized )
		determineAttributes();

	return d->version;
}

QString Port::installedVersion() const
{
	Q_ASSERT( d );
	initPort();
	return  d->port->installedVersion();
}

QString Port::directory() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->directory();
}

QString Port::prefix() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->prefix();
}

QString Port::comment() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->comment();
}

QString Port::description() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->description();
}

QString Port::maintainer() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->maintainer();
}

QStringList Port::categories() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->categories();
}

PortList Port::buildDeps() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->buildDeps();
}

PortList Port::runDeps() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->runDeps();
}

QString Port::link() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->link();
}

QStringList Port::files() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->files();
}

QStringList Port::installedFiles() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->installedFiles();
}

bool Port::installed() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->installed();
}

long long Port::size() const
{
	Q_ASSERT( d );
	initPort();
	return d->port->size();
}

void Port::determineAttributes() const
{
	QString pkgName = PortMgr::self().portDb().getEntry( d->origin );
	pkgName = pkgName.left( pkgName.find( '|' ) );

	parsePkgName( pkgName, d->name, d->version );
	d->initialized = true;
}

void Port::initPort() const
{
	if ( !d->port )
		d->port = new ConcretePort( d->origin );
}

// ---------------------------------------------------------------------------

ConcretePort::ConcretePort( const QString &origin ) : PortInterface()
{
	const QString description = PortMgr::self().portDb().getEntry( origin );

	if ( !description.isNull() ) { // ### Error handling!
		const QStringList infoElements = QStringList::split( '|', description, true );

		m_origin = origin;
		Port::parsePkgName( infoElements[ 0 ], m_name, m_version );
		m_directory = infoElements[ 1 ];
		m_prefix = infoElements[ 2 ];
		m_comment = infoElements[ 3 ];
		m_description = infoElements[ 4 ];
		m_maintainer = infoElements[ 5 ];

		m_categories = QStringList::split( ' ', infoElements[ 6 ] );
		/* Some buggy ports belong twice to a single category, e.g.
		  "editors editors russian". Work around that. */
		m_categories = qUnique( m_categories );
		m_buildDeps = parseDeps( infoElements[ 7 ] );
		m_runDeps = parseDeps( infoElements[ 8 ] );
		m_link = infoElements[ 9 ];
		m_size = -1;
	}
}

QString ConcretePort::origin() const
{
	return m_origin;
}

QString ConcretePort::name() const
{
	return m_name;
}

QString ConcretePort::version() const
{
	return m_version;
}

QString ConcretePort::installedVersion() const
{
	const QString pkgName = PortMgr::self().pkgDb().getEntry( QString::fromLatin1( "?" ) + m_origin );

	QString name, version;
	Port::parsePkgName( pkgName, name, version );
	return version;
}

QString ConcretePort::directory() const
{
	return m_directory;
}

QString ConcretePort::prefix() const
{
	return m_prefix;
}

QString ConcretePort::comment() const
{
	return m_comment;
}

QString ConcretePort::description() const
{
	if ( !QFile::exists( m_description ) ) {
		KNotifyClient::event( QString::fromLatin1( "NoPortDescription" ),
			i18n( "The port %1-%2 doesn't seem to have a description, even "
			      "though it's mandatory. Perhaps the port directory %3 isn't "
			      "mounted properly?" )
			      .arg( name() ).arg( version() ).arg( PortMgr::self().portRootDir() ) );
		return QString::null;
	}

	QFile f( m_description );
	if ( !f.open( IO_ReadOnly ) )
		return QString::null;

	QTextStream stream( &f );

	return stream.read();
}

QString ConcretePort::maintainer() const
{
	return m_maintainer;
}

QStringList ConcretePort::categories() const
{
	return m_categories;
}

PortList ConcretePort::runDeps() const
{
	return m_runDeps;
}

PortList ConcretePort::buildDeps() const
{
	return m_buildDeps;
}

QString ConcretePort::link() const
{
	return m_link;
}

QStringList ConcretePort::files() const
{
	return FileListRetriever::fileList( *this );
}

QStringList ConcretePort::installedFiles() const
{
	const QString pkgName = QString::fromLatin1( "%1-%2" )
	                        .arg( name() )
	                        .arg( installedVersion() );

	const QString contentsFileName = PortMgr::self().pkgRootDir() + QString::fromLatin1( "/" ) +
	                                 pkgName + QString::fromLatin1( "/+CONTENTS" );

	QFile contentsFile( contentsFileName );
	if ( !contentsFile.open( IO_ReadOnly ) ) {
		kdWarning() << "Could not open " << contentsFileName << endl;
		return QStringList();
	}

	QStringList files;
	QTextStream stream( &contentsFile );
	while ( !stream.atEnd() ) {
		QString line = stream.readLine();
		if ( line[ 0 ] == '@' || line[ 0 ] == '+' )
			continue;
		files += prefix() + QString::fromLatin1( "/" ) + line;
	}

	return files;
}

bool ConcretePort::installed() const
{
	return !PortMgr::self().pkgDb().getEntry( QString::fromLatin1( "?" ) + m_origin ).isNull();
}

long long ConcretePort::size() const
{
	if ( m_size == -1 )
		const_cast<ConcretePort *>( this )->determineSize();
	return m_size;
}

void ConcretePort::determineSize()
{
	m_size = 0;
	const QStringList files_ = files();
	QStringList::ConstIterator it = files_.begin();
	QStringList::ConstIterator end = files_.end();
	for ( ; it != end; ++it )
		m_size += QFileInfo( *it ).size();
}

PortList ConcretePort::parseDeps( const QString &depString ) const
{
	PortList deps;
	const QStringList tokens = QStringList::split( ' ', depString );
	QStringList::ConstIterator it = tokens.begin();
	QStringList::ConstIterator end = tokens.end();
	for ( ; it != end; ++it )
		deps += Port::fromPkgName( PortMgr::self().portDb().getEntry( *it ),
		                           *it );
	return deps;
}

// vim:ts=4:sw=4:noet:list
