/// -*- C++ -*- (c) 2006, 2007 Petr Rockai <me@mornfall.net>

#include <wibble/string.h>

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

#include <set>
#include <vector>
#include <fstream>
#include <sstream>
#include <iterator>
#include <functional>

#include <dirent.h>

#ifndef EPT_CORE_DESKTOP_H
#define EPT_CORE_DESKTOP_H

namespace ept {
namespace core {
namespace desktop {

typedef enum { Name, Group, ShortDescription, Package, Icon } PropertyId;

template< PropertyId > struct PropertyType {};
template<> struct PropertyType< Name > { typedef std::string T; };
template<> struct PropertyType< Group > { typedef std::string T; };
template<> struct PropertyType< ShortDescription > { typedef std::string T; };
template<> struct PropertyType< Package > { typedef ept::Token T; };
template<> struct PropertyType< Icon > { typedef std::string T; };

typedef std::set< std::string > Categories;

struct Category {
    std::string name;
    operator std::string() const { return name; }
};

inline std::istream &operator >>( std::istream &i, Category &cat ) {
    char c;
    cat.name = "";
    while ( i.peek() != EOF ) {
        c = i.get();
        if ( c == ';' ) return i;
        cat.name += c;
    }
    return i;
}

struct Entry : wibble::mixin::Comparable< Entry > {
    Entry() {}
    Entry( std::string n, std::string g,
           std::string p, std::string d , std::string i )
        : m_name( n ),
          m_package( p ),
          m_description( d ),
          m_icon( i )
    { setCategories( g ); }

    void load( std::string file ) {
        m_id = file;
        std::ifstream i( file.c_str() );
        if ( !i.is_open() )
            return; // throw?
        desktop::File e;
        i >> e;
        i.close();
        desktop::File::Group &g = e.group( "Desktop Entry" );
        m_name = g.entry( "Name" ).value;
        m_description = g.entry( "Comment" ).value;
        if ( m_description == "" )
            m_description = g.entry( "GenericName" ).value;
        m_package = g.entry( "X-AppInstall-Package" ).value;
        // m_group = g.entry( "Categories" ).value;
        m_icon = g.entry( "Icon" ).value;
        setCategories( g.entry( "Categories" ).value );
    }

    void setCategories( std::string s ) {
        std::istringstream i( s );
        m_categories.clear();
        std::remove_copy_if(
            std::istream_iterator< Category >( i ),
            std::istream_iterator< Category >(),
            std::inserter( m_categories, m_categories.begin() ),
            std::bind1st( std::equal_to< std::string >(), "" ) );
    }

    Categories categories() const { return m_categories; }
    bool inCategory( std::string c ) const {
        return m_categories.find( c ) != m_categories.end();
    }
    std::string id() const { return m_id; }
    std::string name() const { return m_name; }
    std::string package() const { return m_package; }
    std::string description() const { return m_description; }
    std::string icon() const { return m_icon; }
    bool operator< ( const Entry &o ) const {
        if ( m_name < o.m_name ) return true;
        if ( m_name == o.m_name )
            if ( m_package < o.m_package ) return true;
        return false;
    }
protected:
    std::string m_name, m_package, m_description, m_icon, m_id;
    bool m_supported, m_free;
    Categories m_categories;
};

struct InternalList {
    std::string dir;
    std::string current;
    mutable Entry entry;
    off_t offset;
    mutable bool loaded;

    InternalList() : dir( "" ), offset( -2 ), loaded( false ) {}
    InternalList( std::string d ) : dir( d ), offset( -1 ), loaded( false )
    {
        firstFile();
    }

    Entry head() const {
        if (!loaded)
            entry.load( current );
        loaded = true;
        return entry;
    }

    bool empty() const {
        return (offset == -2);
    }

    void firstFile() {
        offset = -1;
        nextFile();
    }

    InternalList tail() const {
        InternalList r = *this;
        r.nextFile();
        return r;
    }

    void nextFile() {
        loaded = false;
        DIR *d = opendir( dir.c_str() );
        if ( !d ) {
            offset = -2;
            closedir( d );
            return;
        }

        if ( offset != -1 )
            seekdir( d, offset );

        dirent *ent = 0;
        while ( ( ent = readdir( d ) ) != 0 ) {
            std::string name( ent->d_name );
            if ( name == "." || name == ".." )
                continue;
            if ( !wibble::str::endsWith( name, ".desktop" ) )
                continue;
            current = dir + "/" + name;
            offset = telldir( d );
            closedir( d );
            return;
        }
        closedir( d );
        offset = -2;
    }
};

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

struct GroupPolicy {
    virtual std::string group( const Entry &e )
    {
        return wibble::str::fmt( e.categories() );
    }
    virtual ~GroupPolicy() {}
};

struct Source : core::Source< Source, Setup, PropertyType >
{
    std::string m_dir;

    GroupPolicy m_defaultPolicy;
    GroupPolicy *m_policy;

    Source( std::string dir ) : m_dir( dir ),
                                m_policy( &m_defaultPolicy ) {}

    InternalList listInternal() {
        return InternalList( m_dir );
    }

    Token getToken( Entry i ) {
        Token t;
        t._id = std::string( "desktop:" ) + i.id();
        return t;
    }

    Entry lookupToken( Token t ) {
        Entry e;
        e.load( t.desktop() );
        return e;
    }

    void setGroupPolicy( GroupPolicy *p ) {
        m_policy = p;
    }

    template< PropertyId p >
    typename PropertyType< p >::T getInternal( Entry );

    struct IsInGroup {
        std::string g;
        IsInGroup( std::string _g = "" ) : g( _g ) {}
        bool operator()( Token, std::string gr ) const {
            return gr == g;
        }
    };

    PropertyFilter< Group, IsInGroup >::T group( std::string id )
    {
        return propertyFilter< Group >( IsInGroup( id ) );
    }

    static std::string projectGroup( ComposedList< Name > t ) {
        return t.get< Group >();
    }

    list::Unique< list::Sorted<
                      list::Map< ComposedList< Name >,
                                 __typeof( std::ptr_fun( &projectGroup ) ) > > >
    groupList() {
        return list::unique(
            list::sort( list::map( list< Name >(),
                                   std::ptr_fun( &projectGroup ) ) ) );
    }
};

template<> inline std::string Source::getInternal< Name >( Entry e ) {
    return e.name();
}

template<> inline std::string Source::getInternal< Icon >( Entry e ) {
    return e.icon();
}

template<> inline ept::Token Source::getInternal< Package >( Entry e ) {
    ept::Token t;
    t._id = e.package();
    return t;
}

template<> inline std::string Source::getInternal< Group >( Entry e ) {
    return m_policy->group( e );
}

template<> inline std::string Source::getInternal< ShortDescription >( Entry e ) {
    return e.description();
}

}
}
}

#endif
