// -*- C++ -*-

#include <ept/core/apt/recordparser.h>

#ifndef EPT_APT_RECORD_H
#define EPT_APT_RECORD_H

namespace ept {
namespace core {
namespace record {

struct Source;

struct InternalList {
    Source *m_source;
    size_t m_idx;

    Internal head();
    const Internal head() const;
    bool empty() const;

    InternalList tail() const {
        InternalList t = *this;
        ++ t.m_idx;
        return t;
    }

    InternalList( Source &s )
        : m_source( &s ), m_idx( 0 )
    {}
};

struct Setup {
    typedef ept::Token Token;
    typedef record::Internal Internal;
    typedef record::PropertyId PropertyId;
    typedef record::InternalList InternalList;
};

template<> struct PropertyType< InstalledSize > { typedef int T; };
template<> struct PropertyType< PackageSize > { typedef int T; };

struct Parser: RecordParser
{
    bool parseBool(bool& def, const std::string& str) const
    {
        // Believe it or not, this is what apt does to interpret bool fields
        if (str == "no" || str == "false" || str == "without" ||
            str == "off" || str == "disable")
            return false;

        if (str == "yes" || str == "true" || str == "with" ||
            str == "on" || str == "enable")
            return true;

        return def;
    }

public:
    Parser() : RecordParser() {}
    Parser(const std::string& str) : RecordParser(str) {}

    template< PropertyId p >
    typename PropertyType< p >::T parse( typename PropertyType< p >::T def,
                                         std::string data );

    template< typename T >
    struct Default {
        static T def;
    };

    template< typename T > T parse( const T &def,
                                    const std::string &field ) const;

    template< PropertyId p >
    typename PropertyType< p >::T get(
        const typename PropertyType< p >::T &def
        = Default< typename PropertyType< p >::T >::def ) const
    {
        return parse< typename PropertyType< p >::T >( def,
                                                       lookup( fields[ p ] ) );
    }

};

template< typename T > T Parser::Default< T >::def = T();

template<> inline std::string Parser::get< ShortDescription >(
    const std::string& def ) const
{
    std::string str = lookup( fields[ Description ] );
    if (str == std::string())
        return def;
    size_t pos = str.find("\n");
    if (pos == std::string::npos)
        return str;
    else
        return str.substr(0, pos);
}

template<> inline std::string Parser::get< LongDescription >(
    const std::string& def ) const
{
    std::string str = lookup( fields[ Description ] );
    if (str == std::string())
        return def;
    size_t pos = str.find("\n");
    if (pos == std::string::npos)
        return str;
    else
    {
        // Trim trailing spaces
        for (++pos; pos < str.size() && isspace(str[pos]); ++pos)
            ;
        return str.substr(pos);
    }
}

template<> inline std::string Parser::parse< std::string >(
    const std::string& def, const std::string& str) const
{
    if (str == std::string())
        return def;
    return str;
}

template<> inline int Parser::parse< int >(
    const int& def, const std::string& str) const
{
	if (str == string())
		return def;
	return (size_t)strtoul(str.c_str(), NULL, 10);
}

struct Source : core::Source< Source, Setup, PropertyType >
{
    AptDatabase &m_db;

    /* caching */
    pkgCache::PkgFileIterator lastFile;
    FileFd file;
    size_t lastOffset;

    /* in-order retrieval of records, for InternalList */
    typedef vector< pkgCache::VerFile * > VfList;
    VfList m_vflist;

    VfList &vfList() {
        if ( m_vflist.size() > 0 )
            return m_vflist;

        m_vflist.reserve(m_db.cache().HeaderP->PackageCount + 1);

        // Populate the vector of versions to print
        for (pkgCache::PkgIterator pi = m_db.cache().PkgBegin(); !pi.end(); ++pi)
        {
            if (pi->VersionList == 0)
                continue;

            for( pkgCache::VerIterator vi = pi.VersionList(); !vi.end(); ++vi ) {

                // Choose a valid file that contains the record for this version
                pkgCache::VerFileIterator vfi = vi.FileList();
                for ( ; !vfi.end(); ++vfi )
                    if ((vfi.File()->Flags & pkgCache::Flag::NotSource) == 0)
                        break;

                if ( !vfi.end() )
                    m_vflist.push_back( vfi );
            }
        }

        sort(m_vflist.begin(), m_vflist.end(), localityCompare);
        return m_vflist;
    }

    Source( AptDatabase &db ) : m_db( db ) {}

    InternalList listInternal() {
        return InternalList( *this );
    }

    Internal lookupToken( Token t ) {
        return m_db.lookupVersionFile( m_db.lookupVersion( t ) );
    }

    // Sort a version list by package file locality
    static bool localityCompare(const pkgCache::VerFile* a,
                                const pkgCache::VerFile* b)
    {
        if (a == 0 && b == 0)
            return false;
        if (a == 0)
            return true;
        if (b == 0)
            return false;

        if (a->File == b->File)
            return a->Offset < b->Offset;
        return a->File < b->File;
    }

    void invalidate() {
        core::Source< Source, Setup, PropertyType >::invalidate();
        lastFile = pkgCache::PkgFileIterator();
    }

    std::string getRecord( Internal vfi ) {
        if ( vfi.Cache() == 0 || vfi.end() )
            return "";

        if ((lastFile.Cache() == 0)
            || vfi->File + m_db.cache().PkgFileP != lastFile)
        {
            lastFile = pkgCache::PkgFileIterator(
                m_db.cache(), vfi->File + m_db.cache().PkgFileP);
            if (!lastFile.IsOk())
                throw wibble::exception::System(
                    std::string("Reading the"
                                " data record for a package from file ")
                    + lastFile.FileName() );
            if (file.IsOpen())
                file.Close();
            if (!file.Open(lastFile.FileName(), FileFd::ReadOnly))
                throw wibble::exception::System( std::string("Opening file ")
                                                 + lastFile.FileName() );
            lastOffset = 0;
        }

        // If we start near were we ended, avoid a seek
        // and enlarge the read a bit
        size_t slack = vfi->Offset - lastOffset;
        if ( slack > 128 ) // mornfall: was 8, making it 128
        {
            slack = 0;
            if ( !file.Seek( vfi->Offset ) )
                throw wibble::exception::System(
                    std::string("Cannot seek to package record in file ")
                    + lastFile.FileName() );
        }

        char buffer[vfi->Size + slack + 1];
        if (!file.Read(buffer, vfi->Size + slack))
            throw wibble::exception::System(
                std::string("Cannot read package "
                            "record in file ") + lastFile.FileName() );

        buffer[vfi->Size + slack] = '\0';
        //cerr << "Data read (slack: " << slack << ")" << endl;

        lastOffset = vfi->Offset + vfi->Size;

        return string(buffer+slack);
    }

    Token getToken( Internal i ) {
        Token t;
        t._id = getInternal< Name >( i ) + "_" + getInternal< Version >( i );
        return t;
    }

    template< PropertyId p >
    typename PropertyType< p >::T getInternal( Internal i ) {
        Parser rec( getRecord( i ) );
        return rec.get< p >();
    }
};

template<> inline std::string Source::getInternal< Record >( Internal i ) {
    assert( !i.end() );
    return getRecord( i );
}

inline const Internal InternalList::head() const {
    return pkgCache::VerFileIterator( m_source->m_db.cache(),
                                      m_source->vfList()[ m_idx ] );
}

inline bool InternalList::empty() const {
    return m_idx == m_source->vfList().size();
}


}
}
}

#endif
