// -*- C++ -*-

#include <string>
#include <wibble/test.h>

#include <ept/token.h>
#include <ept/core/source.h>

#include <iostream>
#include <wibble/exception.h>

#include <apt-pkg/pkgcache.h>
#include <apt-pkg/sourcelist.h>
#include <apt-pkg/error.h>
#include <apt-pkg/policy.h>
#include <apt-pkg/cachefile.h>
#include <apt-pkg/progress.h>
#include <apt-pkg/pkgcachegen.h>
#include <apt-pkg/init.h>

#ifndef EPT_APT_H
#define EPT_APT_H

namespace ept {
namespace core {

struct AptException : wibble::exception::Generic {
    std::string desc() const throw () {
        return "APT Error";
    }

    AptException( const std::string &ctx ) : Generic( ctx ) {
        while ( !_error->empty() ) {
            std::string err;
            _error->PopMessage( err );
            std::cerr << err << std::endl;
            addContext( err );
        }
    }
};

struct PackageState {
    enum Query {
        Install = 1 << 0,
        Upgrade = 1 << 1,
        Keep = 1 << 2,
        Remove = 1 << 3,
        Installed = 1 << 4,
        Upgradable = 1 << 5,
        NowBroken = 1 << 6,
        WillBreak = 1 << 7,
        ReInstall = 1 << 8,
        Purge = 1 << 9,
        Hold = 1 << 10,
        Valid = 1 << 11
    };

    typedef unsigned state;
    
    operator unsigned() { return m_state; };
    
    PackageState &operator=( unsigned i ) {
        m_state = i;
        return *this;
    }
    
    PackageState &operator|=( const PackageState &s ) {
        m_state |= s.m_state;
        return *this;
    }
    
    PackageState( unsigned a ) {
        m_state = a;
    }
    
    PackageState() : m_state( 0 ) {}

    // FIXME this probably needs to be used consistently in core and out of core
    bool isValid() const { return m_state & Valid; }
    // FIXME compatibility API for non-core apt
    bool isInstalled() const { return installed(); }

    bool install() const { return m_state & Install; }
    // reinstall() implies install()
    bool reinstall() const { return m_state & ReInstall; }
    bool remove() const { return m_state & Remove; }
    // purge() implies remove()
    bool purge() const { return m_state & Purge; }
    bool keep() const { return m_state & Keep; }
    bool willBreak() const { return m_state & WillBreak; }
    // upgrade() implies install()
    bool upgrade() const { return hasNewVersion() && install(); }
    // newInsstal() implies install()
    bool newInstall() const { return !installed() && install(); }
    bool hold() const { return m_state & Hold; }

    bool installed() const { return m_state & Installed; }
    bool hasNewVersion() const { return m_state & Upgradable; }
    bool upgradable() const { return hasNewVersion() && !hold(); }
    bool held() const { return hasNewVersion() && hold(); }
    bool nowBroken() const { return m_state & NowBroken; }

    bool modify() const { return install() || remove(); }
    
protected:
    unsigned m_state;
};

time_t aptTimestamp();

// wrap the apt's database
struct AptDatabase {
    pkgCache &cache() {
        if ( !m_cache )
            openCache();
        return *m_cache;
    }

    pkgDepCache &state() {
        if ( !m_state )
            openState();
        return *m_state;
    }

    pkgPolicy &policy() {
        if ( !m_policy )
            openCache();
        return *m_policy;
    }

    OpProgress *m_progress;
    bool m_tryWriteable;
    bool m_writeable;

    time_t timestamp() {
        return aptTimestamp();
    }

    AptDatabase() {
        m_cache = 0;
        m_state = 0;
        m_policy = 0;
        m_progress = new OpProgress();
        m_tryWriteable = true;
        m_writeable = false;
    }

    void setProgress( OpProgress *p ) {
        m_progress = p;
    }

    bool writeable() {
        if ( !m_cache )
            openCache();
        return m_writeable;
    }

    void openState() {
        m_state = new pkgDepCache( &cache(), m_policy );
        m_state->Init( m_progress );
        m_progress->Done();
    }

    void openCache() {
        if ( !_config->FindB("Initialized") ) {
            pkgInitConfig(*_config);
            _config->Set("Initialized", 1);
            pkgInitSystem(*_config, _system);
        }

        m_writeable = m_tryWriteable;

        if ( m_tryWriteable ) {
            try {
                _system->Lock();
            } catch ( std::exception e ) {
                m_tryWriteable = false;
                openCache();
                m_tryWriteable = true;
                throw;
            }
        }

        pkgSourceList list;
        if ( list.ReadMainList() == false ) {
            _error->DumpErrors();
            throw wibble::exception::System(
                "The list of sources could not be read." );
        }

        MMap *m = 0;
        bool Res = pkgMakeStatusCache( list, *m_progress, &m, !m_writeable );

        if ( !Res ) {
            std::cerr << "The package lists or status file "
                "could not be parsed or opened." << std::endl;
            throw AptException(
                "The package lists or status file "
                "could not be parsed or opened." );
        }

        m_cache = new pkgCache( m, true );
        m_policy = new pkgPolicy( m_cache );
        if ( ReadPinFile( *m_policy )  == false )
            throw wibble::exception::System( "error reading pin file" );
        m_progress->Done();
    }

    void invalidate() {
        if ( _config->FindB("Initialized") ) {
            _system->UnLock();
        }

        delete m_state;
        m_state = 0;
        delete m_policy;
        m_policy = 0;
        delete m_cache;
        m_cache = 0;
    }

    pkgCache::VerIterator anyVersion( pkgCache::PkgIterator pi ) {
	if ( pi.end() ) return pkgCache::VerIterator();
        return pi.VersionList();
    }

    Token candidateVersion( Token p ) {
	pkgCache::PkgIterator pi = cache().FindPkg( p.package() );
	if ( pi.end() ) return Token();
	pkgCache::VerIterator vi = policy().GetCandidateVer( pi );
	if ( vi.end() ) return Token();

	Token t; t._id = p.package() + "_" + vi.VerStr();
        return t;
    }

    pkgCache::VerIterator candidateVersion( pkgCache::PkgIterator pi ) {
	if ( pi.end() ) return pkgCache::VerIterator();
	pkgCache::VerIterator vi = policy().GetCandidateVer( pi );
	if ( vi.end() ) return pkgCache::VerIterator();
        return vi;
    }

    pkgCache::VerIterator installedVersion( pkgCache::PkgIterator pi ) {
	if ( pi.end() ) return pkgCache::VerIterator();
	pkgCache::VerIterator vi = pkgCache::VerIterator( cache(),
                                                          cache().VerP + pi->CurrentVer );
	if ( vi.end() ) return pkgCache::VerIterator();
        return vi;
    }

    pkgCache::PkgIterator lookupPackage( Token t ) {
        return cache().FindPkg( t.package() );
    }

    pkgCache::VerIterator lookupVersion( Token t ) {
        if ( !t.hasVersion() )
            t = candidateVersion( t );
        pkgCache::PkgIterator pi = lookupPackage( t );
        if ( pi.end() )
            return pkgCache::VerIterator();
        for (pkgCache::VerIterator vi = pi.VersionList(); !vi.end(); ++vi)
            if ( t.version() == vi.VerStr() )
                return vi;
        return pkgCache::VerIterator();
    }

    static pkgCache::VerFileIterator lookupVersionFile(
        pkgCache::VerIterator vi )
    {
        if ( vi.end() )
            return pkgCache::VerFileIterator();
        pkgCache::VerFileIterator vfi = vi.FileList();
        for ( ; !vfi.end(); vfi++ )
            if ( ( vfi.File()->Flags & pkgCache::Flag::NotSource ) == 0)
                break;
        if ( vfi.end() )
            vfi = vi.FileList();
        return vfi;
    }

    PackageState invalidState() {
        return PackageState( PackageState::NowBroken | PackageState::Keep );
    }

    PackageState packageState( pkgCache::PkgIterator P )
    {
        PackageState s = 0;
        if ( P.end() )
            return invalidState();
        if ( ! ( P->CurrentState == pkgCache::State::ConfigFiles
                 || P->CurrentState == pkgCache::State::NotInstalled ) )
            s |= PackageState::Installed;
        if ( s & PackageState::Installed &&
             candidateVersion( P ) != installedVersion( P ) )
            s |= PackageState::Upgradable;
        pkgDepCache::StateCache S = state()[ P ];
        if ( S.Install() )
            s |= PackageState::Install;
        if ( ( S.iFlags & pkgDepCache::ReInstall )
             == pkgDepCache::ReInstall )
            s |= PackageState::ReInstall;
        if ( S.Keep() )
            s |= PackageState::Keep;
        if ( S.Delete() )
            s |= PackageState::Remove;
        if ( ( S.iFlags & pkgDepCache::Purge ) == pkgDepCache::Purge )
            s |= PackageState::Purge;
        if ( S.NowBroken() )
            s |= PackageState::NowBroken;
        if ( S.InstBroken() )
            s |= PackageState::WillBreak;
        if ( P->SelectedState == pkgCache::State::Hold )
            s |= PackageState::Hold;
        return s;
    }

    PackageState packageState( Token t ) {
        t = validate( t );
        if ( t.valid() )
            return packageState( lookupPackage( t ) );
        return invalidState();
    }

    Token validate( Token t ) {
        if ( t.hasVersion() )
            return lookupVersion( t ).end() ? Token() : t;
        return lookupPackage( t ).end() ? Token() : t;
    }

    ~AptDatabase() {
        invalidate();
    }

protected:
    pkgCache *m_cache;
    pkgDepCache *m_state;
    pkgPolicy *m_policy;
};

template< typename Internal >
struct AptInternalList {
    Internal m_head;
    typedef Internal Type;
    AptInternalList tail() const {
        AptInternalList t = *this;
        t.m_head++;
        return t;
    }
    const Internal &head() const { return m_head; }
    Internal &head() { return m_head; }
    bool empty() const { return m_head.end(); }
    AptInternalList( Internal head ) : m_head( head ) {}
    AptInternalList() {}
};

namespace version {

typedef enum { Package, VersionString, Section, Architecture,
               Depends, Recommends, Record } PropertyId;

typedef pkgCache::VerIterator Internal;
template< PropertyId > struct PropertyType {};

}

namespace package {

typedef enum { Name, Versions, AnyVersion, State, CandidateVersion,
               InstalledVersion } PropertyId;
typedef pkgCache::PkgIterator Internal;
template< PropertyId > struct PropertyType {};

struct VersionList {
    version::Internal m_head;
    VersionList tail() const;
    const version::Internal &head() const { return m_head; }
    version::Internal &head() { return m_head; }
};

typedef AptInternalList< Internal > InternalList;

}

namespace record {

typedef enum { Record, Name, Priority, Section, InstalledSize, Maintainer,
               Architecture, SourcePackage, Version, Description,
               ShortDescription, LongDescription, PackageSize } PropertyId;

extern const char *fields[];

typedef pkgCache::VerFileIterator Internal;

template< PropertyId > struct PropertyType {
    typedef std::string T;
};

}
}
}

#include <ept/core/apt/package.h>
#include <ept/core/apt/version.h>
#include <ept/core/apt/record.h>

#endif
