#include "bu/myriadcache.h"
#include "bu/uuid.h"
#include "bu/string.h"
#include "bu/sio.h"

class Something : public Bu::CacheObject<Bu::Uuid, Something>
{
friend Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, Something &s );
friend Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const Something &s );
public:
    Something()
    {
    }
    
    Something( const Bu::String &sName ) :
        uId( Bu::Uuid::generate() ),
        sName( sName )
    {
    }

    virtual ~Something()
    {
    }

    virtual Bu::Uuid getKey() const
    {
        return uId;
    }

    Bu::String getName() const
    {
        return sName;
    }

    void setName( const Bu::String &sNewName )
    {
        sName = sNewName;
        changed();
    }

    virtual Bu::String toString() const=0;

private:
    Bu::Uuid uId;
    Bu::String sName;
};

class SubSomethingA : public Something
{
friend Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, SubSomethingA &s );
friend Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const SubSomethingA &s );
public:
    SubSomethingA()
    {
    }

    SubSomethingA( const Bu::String &sName, int iNumber ) :
        Something( sName ),
        iNumber( iNumber )
    {
    }
    
    virtual Bu::String toString() const
    {
        return Bu::String("[typeA] %1 (%2)").arg( getName() ).arg( iNumber );
    }

private:
    int iNumber;
};

class SubSomethingB : public Something
{
friend Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, SubSomethingB &s );
friend Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const SubSomethingB &s );
public:
    SubSomethingB()
    {
    }

    SubSomethingB( const Bu::String &sName, const Bu::String &sString ) :
        Something( sName ),
        sString( sString )
    {
    }
    
    virtual Bu::String toString() const
    {
        return Bu::String("[typeB] %1 (%2)").arg( getName() ).arg( sString );
    }

private:
    Bu::String sString;
};

Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, Something &s )
{
    return ar >> s.uId >> s.sName;
}

Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const Something &s )
{
    return ar << s.uId << s.sName;
}

Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, SubSomethingA &s )
{
    return ar >> (Something &)s >> s.iNumber;
}

Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const SubSomethingA &s )
{
    return ar << (Something &)s << s.iNumber;
}

Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, SubSomethingB &s )
{
    return ar >> (Something &)s >> s.sString;
}

Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const SubSomethingB &s )
{
    return ar << (Something &)s << s.sString;
}
    
namespace Bu
{
    template<>
    void _cacheObjectSave<const Something>( Bu::Stream &s, const Something *pObject )
    {
        Bu::Archive ar( s, Bu::Archive::save );
        if( typeid(*pObject) == typeid(SubSomethingA) )
        {
            ar << (uint8_t)1 << (SubSomethingA &)*pObject;
        }
        else if( typeid(*pObject) == typeid(SubSomethingB) )
        {
            ar << (uint8_t)2 << (SubSomethingB &)*pObject;
        }
        else
        {
            Bu::println("Not a recognized type!");
            throw Bu::ExceptionBase("Not recognized type!");
        }
    }

    template<>
    Something *_cacheObjectLoad<Bu::Uuid, Something>( Bu::CacheObject<Bu::Uuid, Something>::Initializer &initObj, const Bu::Uuid &rKey, Bu::Stream &s )
    {
        Bu::Archive ar( s, Bu::Archive::load );
        uint8_t uType;
        ar >> uType;
        switch( uType )
        {
            case 1:
                {
                    SubSomethingA *ret = initObj(new SubSomethingA());
                    ar >> *ret;
                    return ret;
                }
                break;

            case 2:
                {
                    SubSomethingB *ret = initObj(new SubSomethingB());
                    ar >> *ret;
                    return ret;
                }
                break;

            default:
                throw Bu::ExceptionBase("Flagrant error! Invalid type!");
        }

        return NULL;
    }
}

typedef Bu::CachePtr<Bu::Uuid, Something> SomethingPtr;
typedef Bu::CachePtr<Bu::Uuid, SubSomethingA, Something> SomethingAPtr;
typedef Bu::CachePtr<Bu::Uuid, SubSomethingB, Something> SomethingBPtr;
typedef Bu::MyriadCache<Bu::Uuid, Something> SomethingCache;

int main( int, char *[] )
{
    Bu::File fStore("test.myr", Bu::File::Create|Bu::File::ReadWrite);
    SomethingCache c( fStore );

    SomethingPtr ptr;
    if( time(NULL)%2 )
        ptr = c.insert( new SubSomethingA("Hello", 55) ).cast<Something>();
    else
        ptr = c.insert( new SubSomethingB("Goodbye", "Things") ).cast<Something>();

    Bu::println("Something[%1]: %2").
        arg( ptr.getKey() ).
        arg( ptr->getName() );
    
    SomethingCache::KeyList lKeys = c.getKeys();
    Bu::println("Count: %1").arg( lKeys.getSize() );
    for( SomethingCache::KeyList::iterator i = lKeys.begin(); i; i++ )
    {
        Bu::println(" - %1: '%2'").arg( *i ).arg( c.get( *i )->toString() );
    }
    Bu::println("Count: %1").arg( c.getSize() );

    SomethingPtr p2 = c.get( ptr.getKey() );
    Bu::println("%1 == %2").arg( p2->getName() ).arg( ptr->getName() );
    SomethingPtr p3 = ptr.cast<Something>();
    Bu::println("%1: %2").arg( p3.getKey() ).arg( p3->getName() );

    return 0;
}

/*
int main( int argc, char *argv[] )
{
    Bu::File fStore("test.myr", Bu::File::Create|Bu::File::ReadWrite);
    SomethingCache c( fStore );

    for( int j = 1; j < argc; j++ )
    {
        SomethingPtr ptr = c.insert( new Something(argv[j]) );

        Bu::println("Something[%1]: %2").
            arg( ptr.getKey() ).
            arg( ptr->getName() );
    }
    
    SomethingCache::KeyList lKeys = c.getKeys();
    Bu::println("Count: %1").arg( lKeys.getSize() );
    int j = 0;
    for( SomethingCache::KeyList::iterator i = lKeys.begin(); i; i++ )
    {
        Bu::println(" - %1: '%2'").arg( *i ).arg( c.get( *i )->getName() );
        if( ((j++)%2) )
            c.erase( *i );
    }
    Bu::println("Count: %1").arg( c.getSize() );

    c._debug();

    SomethingPtr p2;
    SomethingPtr p1( c.get( lKeys.first() ) );

    c._debug();

    {
        SomethingPtr p2( p1 );
        c._debug();
    }
    
    c._debug();

    p2 = p1;
    
    c._debug();

    p1.unbind();
    
    c._debug();

    Bu::println("Name: %1").arg( p1->getName() );

    p1.unbind();
    p1.lock();
    p1.unlock();
    
    c._debug();

    SomethingPtr p3 = c.getLazy( lKeys.first() );

    c._debug();

    {
    SomethingPtr::Locker l( p3 );

    Bu::println("Name again: %1").arg( p3->getName() );

    p3->setName( p3->getName() + " - again" );
    }

    c.sync();

    c._debug();

    return 0;
}
*/
